diff --git a/.changeset/floppy-experts-wash.md b/.changeset/early-hats-read.md
similarity index 63%
rename from .changeset/floppy-experts-wash.md
rename to .changeset/early-hats-read.md
index 79a30b391..341c69fba 100644
--- a/.changeset/floppy-experts-wash.md
+++ b/.changeset/early-hats-read.md
@@ -2,4 +2,4 @@
"@browserbasehq/stagehand": patch
---
-remove unnecessary log
+improve logging in agent
diff --git a/.changeset/fifty-cats-sell.md b/.changeset/fifty-cats-sell.md
deleted file mode 100644
index dfc981460..000000000
--- a/.changeset/fifty-cats-sell.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@browserbasehq/stagehand": minor
----
-
-extract links
diff --git a/.changeset/fluffy-zoos-joke.md b/.changeset/fluffy-zoos-joke.md
new file mode 100644
index 000000000..4485ca6f5
--- /dev/null
+++ b/.changeset/fluffy-zoos-joke.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+move extract handler response log to after URL injection
diff --git a/.changeset/gold-women-sell.md b/.changeset/gold-women-sell.md
new file mode 100644
index 000000000..916342208
--- /dev/null
+++ b/.changeset/gold-women-sell.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+export tool function & type to simplify defining custom tools
diff --git a/.changeset/vast-vans-crash.md b/.changeset/hungry-lemons-push.md
similarity index 55%
rename from .changeset/vast-vans-crash.md
rename to .changeset/hungry-lemons-push.md
index 3fdc06f83..e0197417b 100644
--- a/.changeset/vast-vans-crash.md
+++ b/.changeset/hungry-lemons-push.md
@@ -2,4 +2,4 @@
"@browserbasehq/stagehand": patch
---
-Fixes a redundant unnecessary log
+add waitForTimeout to page
diff --git a/.changeset/mean-jars-cross.md b/.changeset/mean-jars-cross.md
new file mode 100644
index 000000000..5ba161e11
--- /dev/null
+++ b/.changeset/mean-jars-cross.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+Optimize screenshot handling in agent hybrid mode
diff --git a/.changeset/mean-melons-repeat.md b/.changeset/mean-melons-repeat.md
new file mode 100644
index 000000000..dce1d3f47
--- /dev/null
+++ b/.changeset/mean-melons-repeat.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+fix: replaying cached actions (for agent & act) now uses the originally defined model, (instead of default model) when action fails and rerunning inference is needed
diff --git a/.changeset/proud-ads-live.md b/.changeset/proud-ads-live.md
new file mode 100644
index 000000000..696d67821
--- /dev/null
+++ b/.changeset/proud-ads-live.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+Recommend hybrid mode over DOM mode in agent, which is now considered legacy
diff --git a/.changeset/quick-games-try.md b/.changeset/quick-games-try.md
new file mode 100644
index 000000000..da6f7b87b
--- /dev/null
+++ b/.changeset/quick-games-try.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+Add structured output to agent result + ensure close tool is always called
diff --git a/.changeset/silent-wolves-sell.md b/.changeset/silent-wolves-sell.md
new file mode 100644
index 000000000..8e6e1224b
--- /dev/null
+++ b/.changeset/silent-wolves-sell.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+Fix ControlOrMeta keypress event
diff --git a/.changeset/silly-emus-knock.md b/.changeset/silly-emus-knock.md
new file mode 100644
index 000000000..8602be6b1
--- /dev/null
+++ b/.changeset/silly-emus-knock.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+Update agent to only calculate xpath when caching is enabled
diff --git a/.changeset/silly-rooms-grow.md b/.changeset/silly-rooms-grow.md
new file mode 100644
index 000000000..259b22da5
--- /dev/null
+++ b/.changeset/silly-rooms-grow.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+add support for page.waitForSelector()
diff --git a/.changeset/solid-rice-admire.md b/.changeset/solid-rice-admire.md
deleted file mode 100644
index 2f0291d02..000000000
--- a/.changeset/solid-rice-admire.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@browserbasehq/stagehand": minor
----
-
-Added Gemini 2.5 Flash to Google supported models
diff --git a/.changeset/strong-ideas-guess.md b/.changeset/strong-ideas-guess.md
new file mode 100644
index 000000000..4a632a4b1
--- /dev/null
+++ b/.changeset/strong-ideas-guess.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+update agent message handling
diff --git a/.changeset/tiny-pens-serve.md b/.changeset/tiny-pens-serve.md
new file mode 100644
index 000000000..3b42a8de2
--- /dev/null
+++ b/.changeset/tiny-pens-serve.md
@@ -0,0 +1,5 @@
+---
+"@browserbasehq/stagehand": patch
+---
+
+add page.snapshot() for capturing a stringified DOM snapshot of the page, including an xpath map & url map
diff --git a/.cursorrules b/.cursorrules
index fe68bca7b..eb86cb052 100644
--- a/.cursorrules
+++ b/.cursorrules
@@ -1,140 +1,263 @@
# Stagehand Project
-This is a project that uses Stagehand, which amplifies Playwright with `act`, `extract`, and `observe` added to the Page class.
+This is a project that uses Stagehand V3, a browser automation framework with AI-powered `act`, `extract`, `observe`, and `agent` methods.
-`Stagehand` is a class that provides config, a `StagehandPage` object via `stagehand.page`, and a `StagehandContext` object via `stagehand.context`.
+The main class can be imported as `Stagehand` from `@browserbasehq/stagehand`.
-`Page` is a class that extends the Playwright `Page` class and adds `act`, `extract`, and `observe` methods.
-`Context` is a class that extends the Playwright `BrowserContext` class.
+**Key Classes:**
-Use the following rules to write code for this project.
+- `Stagehand`: Main orchestrator class providing `act`, `extract`, `observe`, and `agent` methods
+- `context`: A `V3Context` object that manages browser contexts and pages
+- `page`: Individual page objects accessed via `stagehand.context.pages()[i]` or created with `stagehand.context.newPage()`
-- To take an action on the page like "click the sign in button", use Stagehand `act` like this:
+## Initialize
```typescript
-await page.act("Click the sign in button");
+import { Stagehand } from "@browserbasehq/stagehand";
+
+const stagehand = new Stagehand({
+ env: "LOCAL", // or "BROWSERBASE"
+ verbose: 2, // 0, 1, or 2
+ model: "openai/gpt-4.1-mini", // or any supported model
+});
+
+await stagehand.init();
+
+// Access the browser context and pages
+const page = stagehand.context.pages()[0];
+const context = stagehand.context;
+
+// Create new pages if needed
+const page2 = await stagehand.context.newPage();
```
-- To plan an instruction before taking an action, use Stagehand `observe` to get the action to execute.
+## Act
+
+Actions are called on the `stagehand` instance (not the page). Use atomic, specific instructions:
```typescript
-const [action] = await page.observe("Click the sign in button");
+// Act on the current active page
+await stagehand.act("click the sign in button");
+
+// Act on a specific page (when you need to target a page that isn't currently active)
+await stagehand.act("click the sign in button", { page: page2 });
```
-- The result of `observe` is an array of `ObserveResult` objects that can directly be used as params for `act` like this:
+**Important:** Act instructions should be atomic and specific:
- ```typescript
- const [action] = await page.observe("Click the sign in button");
- await page.act(action);
- ```
+- ✅ Good: "Click the sign in button" or "Type 'hello' into the search input"
+- ❌ Bad: "Order me pizza" or "Type in the search bar and hit enter" (multi-step)
-- When writing code that needs to extract data from the page, use Stagehand `extract`. Explicitly pass the following params by default:
+### Observe + Act Pattern (Recommended)
+
+Cache the results of `observe` to avoid unexpected DOM changes:
+
+```typescript
+const instruction = "Click the sign in button";
+
+// Get candidate actions
+const actions = await stagehand.observe(instruction);
+
+// Execute the first action
+await stagehand.act(actions[0]);
+```
+
+To target a specific page:
```typescript
-const { someValue } = await page.extract({
- instruction: the instruction to execute,
- schema: z.object({
- someValue: z.string(),
- }), // The schema to extract
+const actions = await stagehand.observe("select blue as the favorite color", {
+ page: page2,
});
+await stagehand.act(actions[0], { page: page2 });
```
-## Initialize
+## Extract
+
+Extract data from pages using natural language instructions. The `extract` method is called on the `stagehand` instance.
+
+### Basic Extraction (with schema)
```typescript
-import { Stagehand } from "@browserbasehq/stagehand";
-import StagehandConfig from "./stagehand.config";
+import { z } from "zod/v3";
+
+// Extract with explicit schema
+const data = await stagehand.extract(
+ "extract all apartment listings with prices and addresses",
+ z.object({
+ listings: z.array(
+ z.object({
+ price: z.string(),
+ address: z.string(),
+ }),
+ ),
+ }),
+);
-const stagehand = new Stagehand(StagehandConfig);
-await stagehand.init();
+console.log(data.listings);
+```
+
+### Simple Extraction (without schema)
+
+```typescript
+// Extract returns a default object with 'extraction' field
+const result = await stagehand.extract("extract the sign in button text");
+
+console.log(result);
+// Output: { extraction: "Sign in" }
-const page = stagehand.page; // Playwright Page with act, extract, and observe methods
-const context = stagehand.context; // Playwright BrowserContext
+// Or destructure directly
+const { extraction } = await stagehand.extract(
+ "extract the sign in button text",
+);
+console.log(extraction); // "Sign in"
```
-## Act
+### Targeted Extraction
-You can cache the results of `observe` and use them as params for `act` like this:
+Extract data from a specific element using a selector:
```typescript
-const instruction = "Click the sign in button";
-const cachedAction = await getCache(instruction);
+const reason = await stagehand.extract(
+ "extract the reason why script injection fails",
+ z.string(),
+ { selector: "/html/body/div[2]/div[3]/iframe/html/body/p[2]" },
+);
+```
-if (cachedAction) {
- await page.act(cachedAction);
-} else {
- try {
- const results = await page.observe(instruction);
- await setCache(instruction, results);
- await page.act(results[0]);
- } catch (error) {
- await page.act(instruction); // If the action is not cached, execute the instruction directly
- }
-}
+### URL Extraction
+
+When extracting links or URLs, use `z.string().url()`:
+
+```typescript
+const { links } = await stagehand.extract(
+ "extract all navigation links",
+ z.object({
+ links: z.array(z.string().url()),
+ }),
+);
```
-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.
+### Extracting from a Specific Page
-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 "Type in the search bar and hit enter".
+```typescript
+// Extract from a specific page (when you need to target a page that isn't currently active)
+const data = await stagehand.extract(
+ "extract the placeholder text on the name field",
+ { page: page2 },
+);
+```
-## Extract
+## Observe
-If you are writing code that needs to extract data from the page, use Stagehand `extract`.
+Plan actions before executing them. Returns an array of candidate actions:
```typescript
-const signInButtonText = await page.extract("extract the sign in button text");
+// Get candidate actions on the current active page
+const [action] = await stagehand.observe("Click the sign in button");
+
+// Execute the action
+await stagehand.act(action);
```
-You can also pass in params like an output schema in Zod, and a flag to use text extraction:
+Observing on a specific page:
```typescript
-const data = await page.extract({
- instruction: "extract the sign in button text",
- schema: z.object({
- text: z.string(),
- }),
+// Target a specific page (when you need to target a page that isn't currently active)
+const actions = await stagehand.observe("find the next page button", {
+ page: page2,
});
+await stagehand.act(actions[0], { page: page2 });
```
-`schema` is a Zod schema that describes the data you want to extract. To extract an array, make sure to pass in a single object that contains the array, as follows:
+## Agent
+
+Use the `agent` method to autonomously execute complex, multi-step tasks.
+
+### Basic Agent Usage
```typescript
-const data = await page.extract({
- instruction: "extract the text inside all buttons",
- schema: z.object({
- text: z.array(z.string()),
- }),
- useTextExtract: true, // Set true for larger-scale extractions (multiple paragraphs), or set false for small extractions (name, birthday, etc)
+const page = stagehand.context.pages()[0];
+await page.goto("https://www.google.com");
+
+const agent = stagehand.agent({
+ model: "google/gemini-2.0-flash",
+ executionModel: "google/gemini-2.0-flash",
+});
+
+const result = await agent.execute({
+ instruction: "Search for the stock price of NVDA",
+ maxSteps: 20,
});
+
+console.log(result.message);
```
-## Agent
+### Computer Use Agent (CUA)
-Use the `agent` method to automonously execute larger tasks like "Get the stock price of NVDA"
+For more advanced scenarios using computer-use models:
```typescript
-// Navigate to a website
-await stagehand.page.goto("https://www.google.com");
+const agent = stagehand.agent({
+ mode: "cua", // Enable Computer Use Agent mode
+ model: "anthropic/claude-sonnet-4-20250514",
+ // or "google/gemini-2.5-computer-use-preview-10-2025"
+ systemPrompt: `You are a helpful assistant that can use a web browser.
+ Do not ask follow up questions, the user will trust your judgement.`,
+});
+
+await agent.execute({
+ instruction: "Apply for a library card at the San Francisco Public Library",
+ maxSteps: 30,
+});
+```
+### Agent with Custom Model Configuration
+
+```typescript
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,
+ model: {
+ modelName: "google/gemini-2.5-computer-use-preview-10-2025",
+ apiKey: process.env.GEMINI_API_KEY,
},
+ systemPrompt: `You are a helpful assistant.`,
});
+```
-// Execute the agent
-await agent.execute(
- "Apply for a library card at the San Francisco Public Library"
-);
+### Agent with Integrations (MCP/External Tools)
+
+```typescript
+const agent = stagehand.agent({
+ integrations: [`https://mcp.exa.ai/mcp?exaApiKey=${process.env.EXA_API_KEY}`],
+ systemPrompt: `You have access to the Exa search tool.`,
+});
+```
+
+## Advanced Features
+
+### DeepLocator (XPath Targeting)
+
+Target specific elements across shadow DOM and iframes:
+
+```typescript
+await page
+ .deepLocator("/html/body/div[2]/div[3]/iframe/html/body/p")
+ .highlight({
+ durationMs: 5000,
+ contentColor: { r: 255, g: 0, b: 0 },
+ });
+```
+
+### Multi-Page Workflows
+
+```typescript
+const page1 = stagehand.context.pages()[0];
+await page1.goto("https://example.com");
+
+const page2 = await stagehand.context.newPage();
+await page2.goto("https://example2.com");
+
+// Act/extract/observe operate on the current active page by default
+// Pass { page } option to target a specific page
+await stagehand.act("click button", { page: page1 });
+await stagehand.extract("get title", { page: page2 });
```
diff --git a/.env.example b/.env.example
index f7b468d6f..f25a24d6e 100644
--- a/.env.example
+++ b/.env.example
@@ -9,4 +9,4 @@ ENABLE_CACHING=false
EVAL_MODELS="gpt-4o,claude-3-5-sonnet-latest"
EXPERIMENTAL_EVAL_MODELS="gpt-4o,claude-3-5-sonnet-latest,o1-mini,o1-preview"
EVAL_CATEGORIES="observe,act,combination,extract,experimental"
-STAGEHAND_API_URL="http://localhost:80"
+AGENT_EVAL_MAX_STEPS=50
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..47f85d4d2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,76 @@
+---
+name: Bug report
+about: Detailed descriptions help us resolve faster
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Before submitting an issue, please:**
+
+- [ ] Check the [documentation](https://docs.stagehand.dev/) for relevant information
+- [ ] Search existing [issues](https://github.com/browserbase/stagehand/issues) to avoid duplicates
+
+## Environment Information
+
+Please provide the following information to help us reproduce and resolve your issue:
+
+**Stagehand:**
+
+- Language/SDK: [TypeScript, Python, MCP…]
+- Stagehand version: [e.g., 1.0.0]
+
+**AI Provider:**
+
+- Provider: [e.g., OpenAI, Anthropic, Azure OpenAI]
+- Model: [e.g., gpt-4o, claude-3-7-sonnet-latest]
+
+## Issue Description
+
+```
+[Describe the current behavior here]
+
+```
+
+### Steps to Reproduce
+
+1.
+2.
+3.
+
+### Minimal Reproduction Code
+
+```tsx
+// Your minimal reproduction code here
+import { Stagehand } from '@browserbase/stagehand';
+
+const stagehand = new Stagehand({
+ // IMPORTANT: include your stagehand config
+});
+
+// Steps that reproduce the issue
+
+```
+
+### Error Messages / Log trace
+
+```
+[Paste error messages/logs here]
+
+```
+
+### Screenshots / Videos
+
+```
+[Attach screenshots or videos here]
+
+```
+
+### Related Issues
+
+Are there any related issues or PRs?
+
+- Related to: #[issue number]
+- Duplicate of: #[issue number]
+- Blocks: #[issue number]
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..75889eb82
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,23 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Are you willing to contribute to implementing this feature or fix?**
+
+- [ ] Yes, I can submit a PR
+- [ ] Yes, but I need guidance
+- [ ] No, I cannot contribute at this time
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 66fdc2dd8..35f415ff1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,147 +7,232 @@ on:
- synchronize
- labeled
- unlabeled
+ paths-ignore:
+ - "packages/docs/**"
env:
- EVAL_MODELS: "gpt-4o,gpt-4o-mini,claude-3-5-sonnet-latest"
- EVAL_CATEGORIES: "observe,act,combination,extract,text_extract,targeted_extract"
+ EVAL_MODELS: "openai/gpt-4.1,google/gemini-2.0-flash,anthropic/claude-haiku-4-5"
+ EVAL_CATEGORIES: "observe,act,combination,extract,targeted_extract,agent"
+ EVAL_MAX_CONCURRENCY: 25
+ EVAL_TRIAL_COUNT: 3
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
+ determine-changes:
+ runs-on: ubuntu-latest
+ outputs:
+ core: ${{ steps.filter.outputs.core }}
+ evals: ${{ steps.filter.outputs.evals }}
+ docs-only: ${{ steps.filter.outputs.docs-only }}
+ steps:
+ - name: Check out repository code
+ uses: actions/checkout@v4
+
+ - uses: dorny/paths-filter@v3
+ id: filter
+ with:
+ filters: |
+ core:
+ - '.github/workflows/ci.yml'
+ - 'packages/core/**'
+ - 'package.json'
+ - 'pnpm-lock.yaml'
+ - 'turbo.json'
+ evals:
+ - 'packages/evals/**'
+ - 'package.json'
+ - 'pnpm-lock.yaml'
+ docs-only:
+ - '**/*.md'
+ - 'examples/**'
+ - '!packages/**/*.md'
+
determine-evals:
+ needs: [determine-changes]
runs-on: ubuntu-latest
outputs:
+ skip-all-evals: ${{ steps.check-labels.outputs.skip-all-evals }}
+ run-regression: ${{ steps.check-labels.outputs.run-regression }}
run-combination: ${{ steps.check-labels.outputs.run-combination }}
run-extract: ${{ steps.check-labels.outputs.run-extract }}
run-act: ${{ steps.check-labels.outputs.run-act }}
run-observe: ${{ steps.check-labels.outputs.run-observe }}
- run-text-extract: ${{ steps.check-labels.outputs.run-text-extract }}
run-targeted-extract: ${{ steps.check-labels.outputs.run-targeted-extract }}
+ run-agent: ${{ steps.check-labels.outputs.run-agent }}
steps:
- id: check-labels
run: |
+ # Check if skip-evals label is present
+ if [[ "${{ contains(github.event.pull_request.labels.*.name, 'skip-evals') }}" == "true" ]]; then
+ echo "skip-evals label found - skipping all evals"
+ echo "skip-all-evals=true" >> $GITHUB_OUTPUT
+ echo "run-regression=false" >> $GITHUB_OUTPUT
+ echo "run-combination=false" >> $GITHUB_OUTPUT
+ echo "run-extract=false" >> $GITHUB_OUTPUT
+ echo "run-act=false" >> $GITHUB_OUTPUT
+ echo "run-observe=false" >> $GITHUB_OUTPUT
+ echo "run-targeted-extract=false" >> $GITHUB_OUTPUT
+ echo "run-agent=false" >> $GITHUB_OUTPUT
+ exit 0
+ fi
+
+ # Skip evals if only docs/examples changed (and not on main)
+ if [[ "${{ needs.determine-changes.outputs.docs-only }}" == "true" && "${{ needs.determine-changes.outputs.core }}" == "false" && "${{ needs.determine-changes.outputs.evals }}" == "false" && "${{ github.ref }}" != "refs/heads/main" ]]; then
+ echo "Only docs/examples changed - skipping evals"
+ echo "skip-all-evals=true" >> $GITHUB_OUTPUT
+ echo "run-regression=false" >> $GITHUB_OUTPUT
+ echo "run-combination=false" >> $GITHUB_OUTPUT
+ echo "run-extract=false" >> $GITHUB_OUTPUT
+ echo "run-act=false" >> $GITHUB_OUTPUT
+ echo "run-observe=false" >> $GITHUB_OUTPUT
+ echo "run-targeted-extract=false" >> $GITHUB_OUTPUT
+ echo "run-agent=false" >> $GITHUB_OUTPUT
+ exit 0
+ fi
+
# Default to running all tests on main branch
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "Running all tests for main branch"
+ echo "skip-all-evals=false" >> $GITHUB_OUTPUT
+ echo "run-regression=true" >> $GITHUB_OUTPUT
echo "run-combination=true" >> $GITHUB_OUTPUT
echo "run-extract=true" >> $GITHUB_OUTPUT
echo "run-act=true" >> $GITHUB_OUTPUT
echo "run-observe=true" >> $GITHUB_OUTPUT
- echo "run-text-extract=true" >> $GITHUB_OUTPUT
echo "run-targeted-extract=true" >> $GITHUB_OUTPUT
+ echo "run-agent=true" >> $GITHUB_OUTPUT
exit 0
fi
+ # Check for skip-regression-evals label
+ if [[ "${{ contains(github.event.pull_request.labels.*.name, 'skip-regression-evals') }}" == "true" ]]; then
+ echo "skip-regression-evals label found - regression evals will be skipped"
+ echo "run-regression=false" >> $GITHUB_OUTPUT
+ else
+ echo "Regression evals will run by default"
+ echo "run-regression=true" >> $GITHUB_OUTPUT
+ fi
+
# Check for specific labels
+ echo "skip-all-evals=false" >> $GITHUB_OUTPUT
echo "run-combination=${{ contains(github.event.pull_request.labels.*.name, 'combination') }}" >> $GITHUB_OUTPUT
echo "run-extract=${{ contains(github.event.pull_request.labels.*.name, 'extract') }}" >> $GITHUB_OUTPUT
echo "run-act=${{ contains(github.event.pull_request.labels.*.name, 'act') }}" >> $GITHUB_OUTPUT
echo "run-observe=${{ contains(github.event.pull_request.labels.*.name, 'observe') }}" >> $GITHUB_OUTPUT
- echo "run-text-extract=${{ contains(github.event.pull_request.labels.*.name, 'text-extract') }}" >> $GITHUB_OUTPUT
echo "run-targeted-extract=${{ contains(github.event.pull_request.labels.*.name, 'targeted-extract') }}" >> $GITHUB_OUTPUT
+ echo "run-agent=${{ contains(github.event.pull_request.labels.*.name, 'agent') }}" >> $GITHUB_OUTPUT
run-lint:
+ needs: [determine-changes]
+ if: needs.determine-changes.outputs.core == 'true' || needs.determine-changes.outputs.evals == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
+ cache: "pnpm"
- name: Install dependencies
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
+ run: pnpm install --frozen-lockfile
- name: Run Lint
- run: npm run lint
+ run: pnpm run lint
run-build:
+ needs: [determine-changes]
+ if: needs.determine-changes.outputs.core == 'true' || needs.determine-changes.outputs.evals == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
+ cache: "pnpm"
- name: Install dependencies
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
+ run: pnpm install --frozen-lockfile
- name: Run Build
- run: npm run build
-
- run-e2e-tests:
- needs: [run-lint, run-build]
- runs-on: ubuntu-latest
- timeout-minutes: 50
- env:
- HEADLESS: true
- steps:
- - name: Check out repository code
- uses: actions/checkout@v4
+ run: pnpm run build
- - name: Set up Node.js
- uses: actions/setup-node@v4
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
with:
- node-version: "20"
+ name: build-artifacts
+ path: |
+ packages/core/dist/**
+ packages/core/lib/**
+ retention-days: 1
- - name: Install dependencies
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
-
- - name: Install Playwright browsers
- run: npm exec playwright install --with-deps
-
- - name: Build Stagehand
- run: npm run build
-
- - name: Run E2E Tests (Deterministic Playwright)
- run: npm run e2e
+ - name: Run Vitest
+ run: pnpm --filter @browserbasehq/stagehand run test:vitest
run-e2e-local-tests:
needs: [run-lint, run-build]
runs-on: ubuntu-latest
timeout-minutes: 50
+ if: >
+ github.event_name == 'push' ||
+ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
env:
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
+ BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
+ BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
HEADLESS: true
steps:
- name: Check out repository code
uses: actions/checkout@v4
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
+ cache: "pnpm"
- - name: Install dependencies
+ - name: Install stable Chromium
run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
+ set -euo pipefail
+ CHROME_VERSION=$(curl -s https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json | jq -r '.channels.Stable.version')
+ DOWNLOAD_URL="https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-linux64.zip"
+ INSTALL_DIR="${RUNNER_TEMP}/chrome-stable"
+ mkdir -p "$INSTALL_DIR"
+ curl -sSL "$DOWNLOAD_URL" -o "$INSTALL_DIR/chrome-linux64.zip"
+ unzip -q "$INSTALL_DIR/chrome-linux64.zip" -d "$INSTALL_DIR"
+ CHROME_BIN="$INSTALL_DIR/chrome-linux64/chrome"
+ chmod +x "$CHROME_BIN"
+ echo "Installed Chromium version: $CHROME_VERSION"
+ "$CHROME_BIN" --version
+ echo "CHROME_PATH=$CHROME_BIN" >> $GITHUB_ENV
- - name: Install Playwright browsers
- run: npm exec playwright install --with-deps
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
- name: Build Stagehand
- run: npm run build
+ run: pnpm run build
- name: Run local E2E Tests (Deterministic Playwright)
- run: npm run e2e:local
+ run: pnpm run e2e:local --log-order=stream
run-e2e-bb-tests:
needs: [run-lint, run-build]
@@ -159,6 +244,7 @@ jobs:
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
HEADLESS: true
@@ -166,29 +252,28 @@ jobs:
- name: Check out repository code
uses: actions/checkout@v4
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
+ cache: "pnpm"
- name: Install dependencies
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
-
- - name: Install Playwright browsers
- run: npm exec playwright install --with-deps
+ run: pnpm install --frozen-lockfile
- name: Build Stagehand
- run: npm run build
+ run: pnpm run build
- name: Run E2E Tests (browserbase)
- run: npm run e2e:bb
+ run: pnpm run e2e:bb --log-order=stream
run-regression-evals:
needs:
- [run-e2e-bb-tests, run-e2e-tests, run-e2e-local-tests, determine-evals]
+ [run-e2e-bb-tests, run-e2e-local-tests, run-build, determine-evals]
+ if: needs.determine-evals.outputs.skip-all-evals != 'true' && needs.determine-evals.outputs.run-regression == 'true'
runs-on: ubuntu-latest
timeout-minutes: 9
outputs:
@@ -196,6 +281,7 @@ jobs:
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
BRAINTRUST_API_KEY: ${{ secrets.BRAINTRUST_API_KEY }}
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
@@ -205,25 +291,25 @@ jobs:
- name: Check out repository code
uses: actions/checkout@v4
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
+ cache: "pnpm"
- name: Install dependencies
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
-
- - name: Build Stagehand
- run: npm run build
+ run: pnpm install --frozen-lockfile
- - name: Install Playwright browsers
- run: npm exec playwright install --with-deps
+ - name: Download build artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: build-artifacts
- name: Run Regression Evals
- run: npm run evals category regression trials=2 concurrency=20 env=BROWSERBASE
+ run: pnpm run evals category regression trials=2 concurrency=20 env=BROWSERBASE
- name: Log Regression Evals Performance
run: |
@@ -242,12 +328,13 @@ jobs:
fi
run-combination-evals:
- needs: [run-regression-evals, determine-evals]
+ needs: [run-regression-evals, run-build, determine-evals]
runs-on: ubuntu-latest
timeout-minutes: 40
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
BRAINTRUST_API_KEY: ${{ secrets.BRAINTRUST_API_KEY }}
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
@@ -267,30 +354,30 @@ jobs:
echo "has_label=true" >> $GITHUB_OUTPUT
fi
+ - name: Setup pnpm
+ if: needs.determine-evals.outputs.run-combination == 'true'
+ uses: pnpm/action-setup@v4
+
- name: Set up Node.js
if: needs.determine-evals.outputs.run-combination == 'true'
uses: actions/setup-node@v4
with:
node-version: "20"
+ cache: "pnpm"
- name: Install dependencies
if: needs.determine-evals.outputs.run-combination == 'true'
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
+ run: pnpm install --frozen-lockfile
- - name: Build Stagehand
+ - name: Download build artifacts
if: needs.determine-evals.outputs.run-combination == 'true'
- run: npm run build
-
- - name: Install Playwright browsers
- if: needs.determine-evals.outputs.run-combination == 'true'
- run: npm exec playwright install --with-deps
+ uses: actions/download-artifact@v4
+ with:
+ name: build-artifacts
- name: Run Combination Evals
if: needs.determine-evals.outputs.run-combination == 'true'
- run: npm run evals category combination
+ run: pnpm run evals category combination
- name: Log Combination Evals Performance
if: needs.determine-evals.outputs.run-combination == 'true'
@@ -307,12 +394,13 @@ jobs:
fi
run-act-evals:
- needs: [run-combination-evals, determine-evals]
+ needs: [run-regression-evals, run-build, determine-evals]
runs-on: ubuntu-latest
timeout-minutes: 25
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
BRAINTRUST_API_KEY: ${{ secrets.BRAINTRUST_API_KEY }}
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
@@ -332,30 +420,30 @@ jobs:
echo "has_label=true" >> $GITHUB_OUTPUT
fi
+ - name: Setup pnpm
+ if: needs.determine-evals.outputs.run-act == 'true'
+ uses: pnpm/action-setup@v4
+
- name: Set up Node.js
if: needs.determine-evals.outputs.run-act == 'true'
uses: actions/setup-node@v4
with:
node-version: "20"
+ cache: "pnpm"
- name: Install dependencies
if: needs.determine-evals.outputs.run-act == 'true'
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
-
- - name: Build Stagehand
- if: needs.determine-evals.outputs.run-act == 'true'
- run: npm run build
+ run: pnpm install --frozen-lockfile
- - name: Install Playwright browsers
+ - name: Download build artifacts
if: needs.determine-evals.outputs.run-act == 'true'
- run: npm exec playwright install --with-deps
+ uses: actions/download-artifact@v4
+ with:
+ name: build-artifacts
- name: Run Act Evals
if: needs.determine-evals.outputs.run-act == 'true'
- run: npm run evals category act
+ run: pnpm run evals category act
- name: Log Act Evals Performance
if: needs.determine-evals.outputs.run-act == 'true'
@@ -375,12 +463,13 @@ jobs:
fi
run-extract-evals:
- needs: [run-act-evals, determine-evals]
+ needs: [run-regression-evals, run-build, determine-evals]
runs-on: ubuntu-latest
timeout-minutes: 50
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
BRAINTRUST_API_KEY: ${{ secrets.BRAINTRUST_API_KEY }}
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
@@ -400,46 +489,37 @@ jobs:
echo "has_label=true" >> $GITHUB_OUTPUT
fi
+ - name: Setup pnpm
+ if: needs.determine-evals.outputs.run-extract == 'true'
+ uses: pnpm/action-setup@v4
+
- name: Set up Node.js
if: needs.determine-evals.outputs.run-extract == 'true'
uses: actions/setup-node@v4
with:
node-version: "20"
+ cache: "pnpm"
- name: Install dependencies
if: needs.determine-evals.outputs.run-extract == 'true'
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
-
- - name: Build Stagehand
- if: needs.determine-evals.outputs.run-extract == 'true'
- run: npm run build
+ run: pnpm install --frozen-lockfile
- - name: Install Playwright browsers
+ - name: Download build artifacts
if: needs.determine-evals.outputs.run-extract == 'true'
- run: npm exec playwright install --with-deps
+ uses: actions/download-artifact@v4
+ with:
+ name: build-artifacts
# 1. Run extract category with domExtract
- name: Run Extract Evals (domExtract)
if: needs.determine-evals.outputs.run-extract == 'true'
- run: npm run evals category extract -- --extract-method=domExtract
+ run: pnpm run evals category extract -- --extract-method=domExtract
- name: Save Extract Dom Results
if: needs.determine-evals.outputs.run-extract == 'true'
run: mv eval-summary.json eval-summary-extract-dom.json
- # 2. Then run extract category with textExtract
- - name: Run Extract Evals (textExtract)
- if: needs.determine-evals.outputs.run-extract == 'true'
- run: npm run evals category extract -- --extract-method=textExtract
-
- - name: Save Extract Text Results
- if: needs.determine-evals.outputs.run-extract == 'true'
- run: mv eval-summary.json eval-summary-extract-text.json
-
- # 3. Log and Compare Extract Evals Performance
+ # 2. Log and Compare Extract Evals Performance
- name: Log and Compare Extract Evals Performance
if: needs.determine-evals.outputs.run-extract == 'true'
run: |
@@ -448,93 +528,20 @@ jobs:
echo "DomExtract Extract category score: $dom_score%"
echo "View domExtract results: https://www.braintrust.dev/app/Browserbase/p/stagehand/experiments/${experimentNameDom}"
- experimentNameText=$(jq -r '.experimentName' eval-summary-extract-text.json)
- text_score=$(jq '.categories.extract' eval-summary-extract-text.json)
- echo "TextExtract Extract category score: $text_score%"
- echo "View textExtract results: https://www.braintrust.dev/app/Browserbase/p/stagehand/experiments/${experimentNameText}"
-
# If domExtract <80% fail CI
if (( $(echo "$dom_score < 80" | bc -l) )); then
echo "DomExtract extract category score is below 80%. Failing CI."
exit 1
fi
- run-text-extract-evals:
- needs: [run-extract-evals, determine-evals]
- runs-on: ubuntu-latest
- timeout-minutes: 120
- env:
- OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- BRAINTRUST_API_KEY: ${{ secrets.BRAINTRUST_API_KEY }}
- BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
- BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
- HEADLESS: true
- EVAL_ENV: browserbase
- steps:
- - name: Check out repository code
- uses: actions/checkout@v4
-
- - name: Check for 'text-extract' label
- id: label-check
- run: |
- if [ "${{ needs.determine-evals.outputs.run-text-extract }}" != "true" ]; then
- echo "has_label=false" >> $GITHUB_OUTPUT
- echo "No label for TEXT-EXTRACT. Exiting with success."
- else
- echo "has_label=true" >> $GITHUB_OUTPUT
- fi
-
- - name: Set up Node.js
- if: needs.determine-evals.outputs.run-text-extract == 'true'
- uses: actions/setup-node@v4
- with:
- node-version: "20"
-
- - name: Install dependencies
- if: needs.determine-evals.outputs.run-text-extract == 'true'
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
-
- - name: Install Playwright browsers
- if: needs.determine-evals.outputs.run-text-extract == 'true'
- run: npm exec playwright install --with-deps
-
- - name: Build Stagehand
- if: needs.determine-evals.outputs.run-text-extract == 'true'
- run: npm run build
-
- - name: Run text_extract Evals (textExtract)
- if: needs.determine-evals.outputs.run-text-extract == 'true'
- run: npm run evals category text_extract -- --extract-method=textExtract
-
- - name: Save text_extract Results
- if: needs.determine-evals.outputs.run-text-extract == 'true'
- run: mv eval-summary.json eval-summary-text_extract-text.json
-
- - name: Log text_extract Evals Performance
- if: needs.determine-evals.outputs.run-text-extract == 'true'
- run: |
- experimentNameText=$(jq -r '.experimentName' eval-summary-text_extract-text.json)
- text_score=$(jq '.categories.text_extract' eval-summary-text_extract-text.json)
- echo "TextExtract text_extract category score: $text_score%"
- echo "View textExtract results: https://www.braintrust.dev/app/Browserbase/p/stagehand/experiments/${experimentNameText}"
-
- # If text_score <80% fail CI
- if (( $(echo "$text_score < 80" | bc -l) )); then
- echo "textExtract text_extract category score is below 80%. Failing CI."
- exit 1
- fi
-
run-observe-evals:
- needs: [run-text-extract-evals, determine-evals]
+ needs: [run-regression-evals, run-build, determine-evals]
runs-on: ubuntu-latest
timeout-minutes: 60
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
BRAINTRUST_API_KEY: ${{ secrets.BRAINTRUST_API_KEY }}
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
@@ -554,30 +561,30 @@ jobs:
echo "has_label=true" >> $GITHUB_OUTPUT
fi
+ - name: Setup pnpm
+ if: needs.determine-evals.outputs.run-observe == 'true'
+ uses: pnpm/action-setup@v4
+
- name: Set up Node.js
if: needs.determine-evals.outputs.run-observe == 'true'
uses: actions/setup-node@v4
with:
node-version: "20"
+ cache: "pnpm"
- name: Install dependencies
if: needs.determine-evals.outputs.run-observe == 'true'
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
-
- - name: Install Playwright browsers
- if: needs.determine-evals.outputs.run-observe == 'true'
- run: npm exec playwright install --with-deps
+ run: pnpm install --frozen-lockfile
- - name: Build Stagehand
+ - name: Download build artifacts
if: needs.determine-evals.outputs.run-observe == 'true'
- run: npm run build
+ uses: actions/download-artifact@v4
+ with:
+ name: build-artifacts
- name: Run Observe Evals
if: needs.determine-evals.outputs.run-observe == 'true'
- run: npm run evals category observe
+ run: pnpm run evals category observe
- name: Log Observe Evals Performance
if: needs.determine-evals.outputs.run-observe == 'true'
@@ -597,12 +604,13 @@ jobs:
fi
run-targeted-extract-evals:
- needs: [run-observe-evals, determine-evals]
+ needs: [run-regression-evals, run-build, determine-evals]
runs-on: ubuntu-latest
timeout-minutes: 60
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
BRAINTRUST_API_KEY: ${{ secrets.BRAINTRUST_API_KEY }}
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
@@ -622,30 +630,30 @@ jobs:
echo "has_label=true" >> $GITHUB_OUTPUT
fi
+ - name: Setup pnpm
+ if: needs.determine-evals.outputs.run-targeted-extract == 'true'
+ uses: pnpm/action-setup@v4
+
- name: Set up Node.js
if: needs.determine-evals.outputs.run-targeted-extract == 'true'
uses: actions/setup-node@v4
with:
node-version: "20"
+ cache: "pnpm"
- name: Install dependencies
if: needs.determine-evals.outputs.run-targeted-extract == 'true'
- run: |
- rm -rf node_modules
- rm -f package-lock.json
- npm install
+ run: pnpm install --frozen-lockfile
- - name: Install Playwright browsers
+ - name: Download build artifacts
if: needs.determine-evals.outputs.run-targeted-extract == 'true'
- run: npm exec playwright install --with-deps
-
- - name: Build Stagehand
- if: needs.determine-evals.outputs.run-targeted-extract == 'true'
- run: npm run build
+ uses: actions/download-artifact@v4
+ with:
+ name: build-artifacts
- name: Run targeted extract Evals
if: needs.determine-evals.outputs.run-targeted-extract == 'true'
- run: npm run evals category targeted_extract -- --extract-method=textExtract
+ run: pnpm run evals category targeted_extract
- name: Log targeted extract Evals Performance
if: needs.determine-evals.outputs.run-targeted-extract == 'true'
@@ -663,3 +671,77 @@ jobs:
echo "Eval summary not found for targeted_extract category. Failing CI."
exit 1
fi
+
+ run-agent-evals:
+ needs: [run-regression-evals, run-build, determine-evals]
+ runs-on: ubuntu-latest
+ timeout-minutes: 90 # Agent evals can be long-running
+ env:
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
+ BRAINTRUST_API_KEY: ${{ secrets.BRAINTRUST_API_KEY }}
+ BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
+ BROWSERBASE_PROJECT_ID: ${{ secrets.BROWSERBASE_PROJECT_ID }}
+ HEADLESS: true
+ EVAL_ENV: browserbase
+ # Use agent models for agent evals in CI
+ EVAL_AGENT_MODELS: "computer-use-preview-2025-03-11,claude-3-7-sonnet-latest"
+ EVAL_TRIAL_COUNT: 2 # Reduce trials for agent evals
+ EVAL_MAX_CONCURRENCY: 10 # Lower concurrency for agent evals
+ steps:
+ - name: Check out repository code
+ uses: actions/checkout@v4
+
+ - name: Check for 'agent' label
+ id: label-check
+ run: |
+ if [ "${{ needs.determine-evals.outputs.run-agent }}" != "true" ]; then
+ echo "has_label=false" >> $GITHUB_OUTPUT
+ echo "No label for AGENT. Exiting with success."
+ else
+ echo "has_label=true" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Setup pnpm
+ if: needs.determine-evals.outputs.run-agent == 'true'
+ uses: pnpm/action-setup@v4
+
+ - name: Set up Node.js
+ if: needs.determine-evals.outputs.run-agent == 'true'
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: "pnpm"
+
+ - name: Install dependencies
+ if: needs.determine-evals.outputs.run-agent == 'true'
+ run: pnpm install --frozen-lockfile
+
+ - name: Download build artifacts
+ if: needs.determine-evals.outputs.run-agent == 'true'
+ uses: actions/download-artifact@v4
+ with:
+ name: build-artifacts
+
+ - name: Run Agent Evals
+ if: needs.determine-evals.outputs.run-agent == 'true'
+ run: pnpm run evals category agent
+
+ - name: Log Agent Evals Performance
+ if: needs.determine-evals.outputs.run-agent == 'true'
+ run: |
+ experimentName=$(jq -r '.experimentName' eval-summary.json)
+ echo "View results at https://www.braintrust.dev/app/Browserbase/p/stagehand/experiments/${experimentName}"
+ if [ -f eval-summary.json ]; then
+ agent_score=$(jq '.categories.agent' eval-summary.json)
+ echo "Agent category score: $agent_score%"
+ # Lower threshold for agent evals since they're complex
+ if (( $(echo "$agent_score < 50" | bc -l) )); then
+ echo "Agent category score is below 50%. Failing CI."
+ exit 1
+ fi
+ else
+ echo "Eval summary not found for agent category. Failing CI."
+ exit 1
+ fi
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
new file mode 100644
index 000000000..d51e7c4c5
--- /dev/null
+++ b/.github/workflows/claude.yml
@@ -0,0 +1,50 @@
+name: Claude Code
+
+on:
+ issue_comment:
+ types: [created]
+ pull_request_review_comment:
+ types: [created]
+ issues:
+ types: [opened, assigned]
+ pull_request_review:
+ types: [submitted]
+
+jobs:
+ claude:
+ if: |
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ issues: write
+ id-token: write
+ actions: read # Required for Claude to read CI results on PRs
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code
+ id: claude
+ uses: anthropics/claude-code-action@v1
+ with:
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
+
+ # This is an optional setting that allows Claude to read CI results on PRs
+ additional_permissions: |
+ actions: read
+
+ # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
+ # prompt: 'Update the pull request description to include a summary of changes.'
+
+ # Optional: Add claude_args to customize behavior and configuration
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
+ # or https://code.claude.com/docs/en/cli-reference for available options
+ # claude_args: '--allowed-tools Bash(gh pr:*)'
+
diff --git a/.github/workflows/feature-parity.yml b/.github/workflows/feature-parity.yml
new file mode 100644
index 000000000..9a40db33e
--- /dev/null
+++ b/.github/workflows/feature-parity.yml
@@ -0,0 +1,147 @@
+name: Feature Parity
+
+on:
+ pull_request:
+ types:
+ - opened
+ - synchronize
+ - labeled
+ - unlabeled
+ paths-ignore:
+ - "packages/docs/**"
+
+jobs:
+ check-parity-label:
+ runs-on: ubuntu-latest
+ if: github.event.action == 'labeled' && github.event.label.name == 'parity'
+ permissions:
+ contents: read
+ pull-requests: write
+ issues: write
+ steps:
+ - name: Check out repository code
+ uses: actions/checkout@v4
+
+ - name: Check user permissions
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ username: context.actor
+ });
+
+ const hasWriteAccess = ['admin', 'write'].includes(permission.permission);
+
+ if (!hasWriteAccess) {
+ // Remove the parity label if user doesn't have write access
+ await github.rest.issues.removeLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ name: 'parity'
+ });
+
+ // Add a comment explaining why the label was removed
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: `❌ **Parity Label Removed**\n\n@${context.actor}, you do not have sufficient permissions to add the 'parity' label. Only users with write access can trigger feature parity issues.\n\nIf you believe this feature should be implemented in the Python SDK, please ask a maintainer to add the label.`
+ });
+
+ throw new Error(`User ${context.actor} does not have write access to add parity label`);
+ }
+
+ console.log(`User ${context.actor} has ${permission.permission} access - proceeding with parity workflow`);
+
+ - name: Generate GitHub App token
+ id: generate-token
+ uses: actions/create-github-app-token@v1
+ with:
+ app-id: ${{ secrets.PARITY_APP_ID }}
+ private-key: ${{ secrets.PARITY_APP_PRIVATE_KEY }}
+ owner: browserbase
+ repositories: stagehand
+
+ - name: Create issue in Python SDK repository
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ steps.generate-token.outputs.token }}
+ script: |
+ const { data: pullRequest } = await github.rest.pulls.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.issue.number,
+ });
+
+ // Get PR comments for additional context
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ });
+
+ // Format comments for the issue description
+ let commentsSection = '';
+ if (comments.length > 0) {
+ commentsSection = '\n\n## Recent Comments\n\n';
+ comments.slice(-3).forEach(comment => {
+ commentsSection += `**@${comment.user.login}** commented:\n`;
+ commentsSection += `${comment.body.substring(0, 500)}${comment.body.length > 500 ? '...' : ''}\n\n`;
+ });
+ }
+
+ // Get list of changed files for context
+ const { data: files } = await github.rest.pulls.listFiles({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.issue.number,
+ });
+
+ const changedFiles = files.map(file => `- \`${file.filename}\``).join('\n');
+
+ const issueTitle = `[Feature Parity] ${pullRequest.title}`;
+ const issueBody = `## Feature Parity Request
+
+ This issue was automatically created from a pull request in the TypeScript Stagehand repository that was labeled with 'parity'.
+
+ ### Original PR Details
+ - **PR**: #${context.issue.number} - ${pullRequest.title}
+ - **Author**: @${pullRequest.user.login}
+ - **Link**: ${pullRequest.html_url}
+
+ ### Description
+ ${pullRequest.body || 'No description provided.'}
+
+ ### Changed Files
+ ${changedFiles}
+
+ ${commentsSection}
+
+ ### Action Required
+ Please review the changes in the original PR and implement equivalent functionality in the Python SDK if applicable.
+
+ ---
+ *This issue was automatically generated by the Feature Parity workflow.*`;
+
+ // Create the issue in the Python repository
+ const { data: issue } = await github.rest.issues.create({
+ owner: 'browserbase',
+ repo: 'stagehand-python',
+ title: issueTitle,
+ body: issueBody,
+ labels: ['parity']
+ });
+
+ console.log(`Created issue: ${issue.html_url}`);
+
+ // Add a comment to the original PR confirming the issue was created
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: `🔄 **Feature Parity Issue Created**\n\nAn issue has been automatically created in the Python SDK repository to track parity implementation:\n${issue.html_url}`
+ });
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 00975f83a..f3c5d1100 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -8,6 +8,7 @@ on:
permissions:
contents: write
pull-requests: write
+ id-token: write
concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -17,38 +18,41 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
- name: Setup Node.js 20.x
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: 20.x
registry-url: "https://registry.npmjs.org"
+ # Ensure npm 11.5.1 or later is installed for Trusted Publishing.
+ - name: Update npm
+ run: npm install -g npm@latest
+
- name: Install dependencies
run: |
rm -rf node_modules
- rm -f package-lock.json
- npm install
+ npm install -g pnpm
+ pnpm install --no-frozen-lockfile
- name: Build
- run: npm run build
+ run: pnpm run build
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1
with:
- publish: npm run release
+ publish: pnpm run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish Canary
if: github.ref == 'refs/heads/main'
run: |
- npm config set //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
git checkout main
- npm run release-canary
+ pnpm run release-canary
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/stagehand-server-release.yml b/.github/workflows/stagehand-server-release.yml
new file mode 100644
index 000000000..08d5fac7d
--- /dev/null
+++ b/.github/workflows/stagehand-server-release.yml
@@ -0,0 +1,307 @@
+name: Release stagehand/server
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - packages/server/package.json
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}
+
+env:
+ OAS_PATH: packages/server/openapi.v3.yaml
+
+jobs:
+ detect:
+ name: Detect server version bump
+ runs-on: ubuntu-latest
+ outputs:
+ release: ${{ steps.meta.outputs.release }}
+ version: ${{ steps.meta.outputs.version }}
+ tag: ${{ steps.meta.outputs.tag }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Determine release metadata
+ id: meta
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ after_version="$(node -p "require('./packages/server/package.json').version")"
+ tag="stagehand-server/v${after_version}"
+
+ latest_tag="$(git tag -l 'stagehand-server/v*' --sort=-v:refname | head -n 1 || true)"
+ latest_version="${latest_tag#stagehand-server/v}"
+
+ release="false"
+ if [ -z "${latest_tag}" ] || [ "${latest_version}" != "${after_version}" ]; then
+ release="true"
+ fi
+
+ echo "release=${release}" >> "$GITHUB_OUTPUT"
+ echo "version=${after_version}" >> "$GITHUB_OUTPUT"
+ echo "tag=${tag}" >> "$GITHUB_OUTPUT"
+
+ - name: Create stagehand/server tag
+ if: steps.meta.outputs.release == 'true'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ TAG="${{ steps.meta.outputs.tag }}"
+ VERSION="${{ steps.meta.outputs.version }}"
+ TARGET_SHA="${{ github.sha }}"
+
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+
+ git fetch --tags --force
+
+ if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
+ echo "Tag already exists: ${TAG}"
+ exit 0
+ fi
+
+ git tag -a "${TAG}" "${TARGET_SHA}" -m "stagehand/server v${VERSION}"
+ git push origin "${TAG}"
+
+ build_binaries:
+ name: Build SEA binaries (${{ matrix.binary_name }})
+ needs: detect
+ if: needs.detect.outputs.release == 'true'
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: ubuntu-latest
+ platform: linux
+ arch: x64
+ binary_name: stagehand-server-linux-x64
+
+ - os: ubuntu-latest
+ platform: linux
+ arch: arm64
+ binary_name: stagehand-server-linux-arm64
+
+ - os: macos-15
+ platform: darwin
+ arch: arm64
+ binary_name: stagehand-server-darwin-arm64
+
+ - os: macos-15-intel
+ platform: darwin
+ arch: x64
+ binary_name: stagehand-server-darwin-x64
+
+ - os: windows-latest
+ platform: win32
+ arch: x64
+ binary_name: stagehand-server-win32-x64.exe
+
+ - os: windows-latest
+ platform: win32
+ arch: arm64
+ binary_name: stagehand-server-win32-arm64.exe
+
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22.x
+ cache: pnpm
+
+ - name: Install dependencies
+ env:
+ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
+ run: pnpm install --frozen-lockfile
+
+ - name: Build binary (Linux/macOS x64/host arch)
+ if: matrix.platform != 'win32' && !(matrix.platform == 'linux' && matrix.arch == 'arm64')
+ shell: bash
+ run: |
+ CI=true pnpm --filter @browserbasehq/stagehand-server build:binary
+
+ - name: Build binary (Linux arm64)
+ if: matrix.platform == 'linux' && matrix.arch == 'arm64'
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ # Intentionally build the core SDK package (not `@browserbasehq/stagehand-server`),
+ # since the SEA bundle depends on the built SDK output and the server's `build`
+ # also runs OpenAPI generation (unnecessary for binary packaging here).
+ pnpm --filter @browserbasehq/stagehand build
+ cd packages/server
+ mkdir -p dist/sea
+
+ pnpm exec esbuild src/server.ts \
+ --bundle \
+ --platform=node \
+ --format=cjs \
+ --outfile=dist/sea/bundle.cjs \
+ --log-level=warning
+
+ node --experimental-sea-config sea-config.json
+
+ BLOB="dist/sea/sea-prep.blob"
+ if [ ! -f "${BLOB}" ]; then
+ echo "Missing ${BLOB}; SEA blob generation failed." >&2
+ exit 1
+ fi
+
+ NODE_VERSION="$(node -p 'process.version')"
+ NODE_TARBALL="node-${NODE_VERSION}-linux-arm64.tar.xz"
+ NODE_URL="https://nodejs.org/dist/${NODE_VERSION}/${NODE_TARBALL}"
+
+ curl -fsSL "${NODE_URL}" -o "${NODE_TARBALL}"
+ tar -xJf "${NODE_TARBALL}"
+
+ NODE_EXE="node-${NODE_VERSION}-linux-arm64/bin/node"
+ if [ ! -f "${NODE_EXE}" ]; then
+ echo "Missing downloaded Node binary at ${NODE_EXE}" >&2
+ exit 1
+ fi
+
+ OUT="dist/sea/${{ matrix.binary_name }}"
+ cp "${NODE_EXE}" "${OUT}"
+ chmod +x "${OUT}"
+
+ pnpm exec postject "${OUT}" NODE_SEA_BLOB "${BLOB}" \
+ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
+
+ ls -lh "${OUT}"
+
+ - name: Build binary (Windows)
+ if: matrix.platform == 'win32' && matrix.arch == 'x64'
+ shell: powershell
+ run: |
+ # Intentionally build the core SDK package (not `@browserbasehq/stagehand-server`),
+ # since the SEA bundle depends on the built SDK output and the server's `build`
+ # also runs OpenAPI generation (unnecessary for binary packaging here).
+ pnpm --filter @browserbasehq/stagehand build
+ Set-Location packages/server
+ node -e "require('fs').mkdirSync('dist/sea',{recursive:true})"
+ pnpm exec esbuild src/server.ts --bundle --platform=node --format=cjs --outfile=dist/sea/bundle.cjs --log-level=warning
+ node --experimental-sea-config sea-config.json
+ $blob = "dist/sea/sea-prep.blob"
+ if (!(Test-Path $blob)) { throw "Missing blob at $blob; SEA blob generation failed." }
+ Copy-Item (Get-Command node).Source -Destination "dist/sea/${{ matrix.binary_name }}"
+ pnpm exec postject "dist/sea/${{ matrix.binary_name }}" NODE_SEA_BLOB $blob `
+ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
+
+ - name: Build binary (Windows arm64)
+ if: matrix.platform == 'win32' && matrix.arch == 'arm64'
+ shell: powershell
+ run: |
+ # Intentionally build the core SDK package (not `@browserbasehq/stagehand-server`),
+ # since the SEA bundle depends on the built SDK output and the server's `build`
+ # also runs OpenAPI generation (unnecessary for binary packaging here).
+ pnpm --filter @browserbasehq/stagehand build
+ Set-Location packages/server
+ node -e "require('fs').mkdirSync('dist/sea',{recursive:true})"
+ pnpm exec esbuild src/server.ts --bundle --platform=node --format=cjs --outfile=dist/sea/bundle.cjs --log-level=warning
+ node --experimental-sea-config sea-config.json
+
+ $blob = "dist/sea/sea-prep.blob"
+ if (!(Test-Path $blob)) { throw "Missing blob at $blob; SEA blob generation failed." }
+
+ $nodeVersion = node -p "process.version"
+ $zipName = "node-$nodeVersion-win-arm64.zip"
+ $url = "https://nodejs.org/dist/$nodeVersion/$zipName"
+
+ $tmp = Join-Path $env:RUNNER_TEMP "node-arm64"
+ Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
+ New-Item -ItemType Directory -Force -Path $tmp | Out-Null
+
+ $zipPath = Join-Path $tmp $zipName
+ Invoke-WebRequest -Uri $url -OutFile $zipPath
+ Expand-Archive -Path $zipPath -DestinationPath $tmp
+
+ $nodeExe = Join-Path $tmp "node-$nodeVersion-win-arm64\\node.exe"
+ if (!(Test-Path $nodeExe)) { throw "Missing downloaded Node binary at $nodeExe" }
+
+ Copy-Item $nodeExe -Destination "dist/sea/${{ matrix.binary_name }}"
+ pnpm exec postject "dist/sea/${{ matrix.binary_name }}" NODE_SEA_BLOB $blob `
+ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ matrix.binary_name }}
+ path: packages/server/dist/sea/${{ matrix.binary_name }}
+ retention-days: 7
+
+ release:
+ name: Publish GitHub Release
+ needs: [detect, build_binaries]
+ if: needs.detect.outputs.release == 'true'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Prepare release assets directory
+ run: mkdir -p release-assets
+
+ - name: Prepare stagehand/server release assets
+ run: |
+ set -euo pipefail
+ cp "${{ env.OAS_PATH }}" "release-assets/openapi.v3.stagehand-server-${{ needs.detect.outputs.version }}.yaml"
+
+ - name: Download SEA binary artifacts
+ uses: actions/download-artifact@v4
+ with:
+ pattern: stagehand-server-*
+ path: release-assets
+ merge-multiple: true
+
+ - name: Create checksums
+ shell: bash
+ run: |
+ set -euo pipefail
+ cd release-assets
+ # Only checksum binaries (exclude openapi yaml). Avoid failing if no matches.
+ shopt -s nullglob
+ files=(stagehand-server-*)
+ bins=()
+ for f in "${files[@]}"; do
+ [[ "$f" == *openapi* ]] && continue
+ [[ -f "$f" ]] && bins+=("$f")
+ done
+ : > checksums.sha256
+ if [ "${#bins[@]}" -gt 0 ]; then
+ shasum -a 256 "${bins[@]}" > checksums.sha256
+ fi
+
+ - name: Publish stagehand/server GitHub release
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: ${{ needs.detect.outputs.tag }}
+ name: stagehand/server v${{ needs.detect.outputs.version }}
+ generate_release_notes: true
+ files: |
+ release-assets/openapi.v3.stagehand-server-${{ needs.detect.outputs.version }}.yaml
+ release-assets/stagehand-server-*
+ release-assets/checksums.sha256
diff --git a/.github/workflows/stainless.yml b/.github/workflows/stainless.yml
new file mode 100644
index 000000000..70703c23d
--- /dev/null
+++ b/.github/workflows/stainless.yml
@@ -0,0 +1,60 @@
+name: Build SDKs for pull request
+
+on:
+ pull_request:
+ types:
+ - opened
+ - synchronize
+ - reopened
+ - closed
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+
+env:
+ STAINLESS_ORG: ${{ vars.STAINLESS_ORG }}
+ STAINLESS_PROJECT: ${{ vars.STAINLESS_PROJECT }}
+ OAS_PATH: packages/server/openapi.v3.yaml
+
+jobs:
+ preview:
+ if: github.event.action != 'closed'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+
+ - name: Run preview builds
+ uses: stainless-api/upload-openapi-spec-action/preview@v1
+ with:
+ stainless_api_key: ${{ secrets.STAINLESS_API_KEY }}
+ org: ${{ env.STAINLESS_ORG }}
+ project: ${{ env.STAINLESS_PROJECT }}
+ oas_path: ${{ env.OAS_PATH }}
+ config_path: stainless.yml
+
+ merge:
+ if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+ - name: Run merge build
+ uses: stainless-api/upload-openapi-spec-action/merge@v1
+ with:
+ stainless_api_key: ${{ secrets.STAINLESS_API_KEY }}
+ org: ${{ env.STAINLESS_ORG }}
+ project: ${{ env.STAINLESS_PROJECT }}
+ oas_path: ${{ env.OAS_PATH }}
+ config_path: stainless.yml
diff --git a/.gitignore b/.gitignore
index e5ea06bbe..f9a77a016 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,12 +9,20 @@ screenshot.png
.env
downloads/
dist/
-evals/**/public
-lib/dom/build/
-evals/public
+.browserbase/
+packages/evals/**/public
+packages/core/lib/dom/build/
+packages/core/lib/v3/dom/build/
+packages/evals/public
*.tgz
evals/playground.ts
tmp/
eval-summary.json
-pnpm-lock.yaml
+package-lock.json
evals/deterministic/tests/BrowserContext/tmp-test.har
+packages/core/lib/version.ts
+packages/core/test-results/
+/examples/inference_summary
+/inference_summary
+.turbo
+.idea
diff --git a/.prettierignore b/.prettierignore
index 9581fb07d..98ad8477c 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,3 +1,19 @@
pnpm-lock.yaml
README.md
-**/*.json
\ No newline at end of file
+**/*.json
+docs/
+.github/
+dist/
+node_modules/
+lib/dom/build/
+lib/v3/dom/build/
+packages/core/dist/
+packages/core/lib/dom/build/
+packages/core/lib/v3/dom/build/
+packages/evals/dist/
+packages/docs/
+*.min.js
+.browserbase/
+.browserbase/**
+**/.browserbase/
+**/.browserbase/**
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b34a6abc..b37ce4d87 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,222 @@
# @browserbasehq/stagehand
+## 3.0.0
+
+### Major Changes
+
+- Removes internal Playwright dependency
+- A generous 20-40% speed increase across `act`, `extract`, & `observe` calls
+- Compatibility with Playwright, Puppeteer, and Patchright
+- Automatic action caching (agent, stagehand.act). Go from CUA → deterministic scripts w/o inference
+- A suite of non AI primitives:
+ - `page`
+ - `locator` (built in closed mode shadow root traversal, with xpaths & css selectors)
+ - `frameLocator`
+ - `deepLocator` (crosses iframes & shadow roots)
+- bun compatibility
+- Simplified extract schemas
+- CSS selector support (id-based support coming soon)
+- Targeted extract and observe across iframes & shadow roots
+- More intuitive type names (observeResult is now action, act accepts an instruction string instead of an action string, solidified ModelConfiguration)
+
+Check the [migration guide](https://docs.stagehand.dev/v3/migrations/v2) for more information
+
+## 2.5.0
+
+### Minor Changes
+
+- [#981](https://github.com/browserbase/stagehand/pull/981) [`8244ab2`](https://github.com/browserbase/stagehand/commit/8244ab247cd679962685ae2f7c54e874ce1fa614) Thanks [@sameelarif](https://github.com/sameelarif)! - Added support for `stagehand.agent` to interact with MCP servers as well as custom tools to be passed in. For more information, reference the [MCP integrations documentation](https://docs.stagehand.dev/best-practices/mcp-integrations)
+
+### Patch Changes
+
+- [#959](https://github.com/browserbase/stagehand/pull/959) [`09b5e1e`](https://github.com/browserbase/stagehand/commit/09b5e1e9c23c845903686db6665cc968ac34efbb) Thanks [@filip-michalsky](https://github.com/filip-michalsky)! - add webvoyager evals
+
+- [#1049](https://github.com/browserbase/stagehand/pull/1049) [`e3734b9`](https://github.com/browserbase/stagehand/commit/e3734b9c98352d5f0a4eca49791b0bbf2130ab41) Thanks [@miguelg719](https://github.com/miguelg719)! - Support local MCP server connections
+
+- [#1025](https://github.com/browserbase/stagehand/pull/1025) [`be85b19`](https://github.com/browserbase/stagehand/commit/be85b19679a826f19702e00f0aae72fce1118ec8) Thanks [@tkattkat](https://github.com/tkattkat)! - add support for custom baseUrl within openai provider
+
+- [#1040](https://github.com/browserbase/stagehand/pull/1040) [`88d1565`](https://github.com/browserbase/stagehand/commit/88d1565c65bb65a104fea2d5f5e862bbbda69677) Thanks [@miguelg719](https://github.com/miguelg719)! - Allow OpenAI CUA to take in an optional baseURL
+
+- [#1046](https://github.com/browserbase/stagehand/pull/1046) [`ab5d6ed`](https://github.com/browserbase/stagehand/commit/ab5d6ede19aabc059badc4247f1cb2c6c9e71bae) Thanks [@tkattkat](https://github.com/tkattkat)! - Add support for gpt-5 in operator agent
+
+## 2.4.4
+
+### Patch Changes
+
+- [#1012](https://github.com/browserbase/stagehand/pull/1012) [`9e8c173`](https://github.com/browserbase/stagehand/commit/9e8c17374fdc8fbe7f26e6cf802c36bd14f11039) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix disabling api validation whenever a customLLM client is provided
+
+## 2.4.3
+
+### Patch Changes
+
+- [#951](https://github.com/browserbase/stagehand/pull/951) [`f45afdc`](https://github.com/browserbase/stagehand/commit/f45afdccc8680650755fee66ffbeac32b41e075d) Thanks [@miguelg719](https://github.com/miguelg719)! - Patch GPT-5 new api format
+
+- [#954](https://github.com/browserbase/stagehand/pull/954) [`261bba4`](https://github.com/browserbase/stagehand/commit/261bba43fa79ac3af95328e673ef3e9fced3279b) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add support for shadow DOMs (open & closed mode) when experimental: true
+
+- [#944](https://github.com/browserbase/stagehand/pull/944) [`8de7bd8`](https://github.com/browserbase/stagehand/commit/8de7bd8635c2051cd8025e365c6c8aa83d81c7e7) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Bump zod version compatibility and add pathing spec
+
+- [#919](https://github.com/browserbase/stagehand/pull/919) [`3d80421`](https://github.com/browserbase/stagehand/commit/3d804210a106a6828c7fa50f8b765b10afd4cc6a) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - enable scrolling inside of iframes
+
+- [#963](https://github.com/browserbase/stagehand/pull/963) [`0ead63d`](https://github.com/browserbase/stagehand/commit/0ead63d6526f6c286362b74b6407c8bebc900e69) Thanks [@tkattkat](https://github.com/tkattkat)! - Properly handle images in evaluator + clean up response parsing logic
+
+- [#961](https://github.com/browserbase/stagehand/pull/961) [`8422828`](https://github.com/browserbase/stagehand/commit/8422828c4cd5fd5ebcf348cfbdb40c768bb76dd9) Thanks [@tkattkat](https://github.com/tkattkat)! - Add more evals for stagehand agent
+
+- [#946](https://github.com/browserbase/stagehand/pull/946) [`b769206`](https://github.com/browserbase/stagehand/commit/b7692060f98a2f49aeeefb90d8789ed034b08ec2) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: unable to act on/get content from some same process iframes
+
+- [#962](https://github.com/browserbase/stagehand/pull/962) [`72d2683`](https://github.com/browserbase/stagehand/commit/72d2683202af7e578d98367893964b33e0828de5) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - handle namespaced elements in xpath build step
+
+## 2.4.2
+
+### Patch Changes
+
+- [#865](https://github.com/browserbase/stagehand/pull/865) [`6b4e6e3`](https://github.com/browserbase/stagehand/commit/6b4e6e3f31d5496cf15728e9018eddeb04839542) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - improve type safety for trimTrailingTextNode
+
+- [#897](https://github.com/browserbase/stagehand/pull/897) [`e77d018`](https://github.com/browserbase/stagehand/commit/e77d0188683ebf596dfb78dfafbbca1dc32993f0) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix selfHeal to remember intially received arguments
+
+- [#920](https://github.com/browserbase/stagehand/pull/920) [`c20adb9`](https://github.com/browserbase/stagehand/commit/c20adb95539fed8c56a4aa413262a9c65a8e6474) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: tab handling on API
+
+- [#882](https://github.com/browserbase/stagehand/pull/882) [`b86df93`](https://github.com/browserbase/stagehand/commit/b86df93b9136aae96292121a29c25f3d74d84bf7) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - remove elements that don't have xpaths from observe response
+
+- [#905](https://github.com/browserbase/stagehand/pull/905) [`023c2c2`](https://github.com/browserbase/stagehand/commit/023c2c273b46d3792d7e5d3c902089487b16b531) Thanks [@tkattkat](https://github.com/tkattkat)! - Delete old images from anthropic cua client
+
+- [#925](https://github.com/browserbase/stagehand/pull/925) [`8c28647`](https://github.com/browserbase/stagehand/commit/8c2864755ecd05c8f7de235d4198deec0dd5f78e) Thanks [@miguelg719](https://github.com/miguelg719)! - Remove \_refreshPageFromApi()
+
+- [#887](https://github.com/browserbase/stagehand/pull/887) [`87e09c6`](https://github.com/browserbase/stagehand/commit/87e09c618940f364ec8af00455a19a17ec63cbd3) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: allow xpaths with prepended 'xpath=' for targeted extract
+
+- [#864](https://github.com/browserbase/stagehand/pull/864) [`a611115`](https://github.com/browserbase/stagehand/commit/a61111525d70b450bdfc43f112380f44899c9e97) Thanks [@miguelg719](https://github.com/miguelg719)! - Temporarily patch custom clients serialization error on api
+
+- [#881](https://github.com/browserbase/stagehand/pull/881) [`69913fe`](https://github.com/browserbase/stagehand/commit/69913fe1dfb8201ae2aeffa5f049fb46ab02cbc2) Thanks [@miguelg719](https://github.com/miguelg719)! - Pass sdk version number to API for debugging
+
+- [#913](https://github.com/browserbase/stagehand/pull/913) [`b1b83a1`](https://github.com/browserbase/stagehand/commit/b1b83a1d334fe76e5f5f9dd32dc92c16b7d40ce6) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - move iframe out of 'experimental'
+
+- [#891](https://github.com/browserbase/stagehand/pull/891) [`be8497c`](https://github.com/browserbase/stagehand/commit/be8497cb6b142cc893cea9692b8c47bd19514c60) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: nested iframe xpath bug
+
+- [#883](https://github.com/browserbase/stagehand/pull/883) [`98704c9`](https://github.com/browserbase/stagehand/commit/98704c9ed225ca25bbde4bb3dc286936e9c54471) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add timeout for JS click
+
+- [#907](https://github.com/browserbase/stagehand/pull/907) [`04978bd`](https://github.com/browserbase/stagehand/commit/04978bdd30d2edcbc69eb9fd91358a16975ea2eb) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - store mapping of CDP frame ID -> page
+
+## 2.4.1
+
+### Patch Changes
+
+- [#856](https://github.com/browserbase/stagehand/pull/856) [`8a43c5a`](https://github.com/browserbase/stagehand/commit/8a43c5a86d4da40cfaedd9cf2e42186928bdf946) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - set download behaviour by default
+
+- [#857](https://github.com/browserbase/stagehand/pull/857) [`890ffcc`](https://github.com/browserbase/stagehand/commit/890ffccac5e0a60ade64a46eb550c981ffb3e84a) Thanks [@miguelg719](https://github.com/miguelg719)! - return "not-supported" for elements inside the shadow-dom
+
+- [#844](https://github.com/browserbase/stagehand/pull/844) [`64c1072`](https://github.com/browserbase/stagehand/commit/64c10727bda50470483a3eb175c02842db0923a1) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - don't automatically close tabs
+
+- [#860](https://github.com/browserbase/stagehand/pull/860) [`b077d3f`](https://github.com/browserbase/stagehand/commit/b077d3f48a97f47a71ccc79ae39b41e7f07f9c04) Thanks [@miguelg719](https://github.com/miguelg719)! - Set default schema on extract options with no schema
+
+- [#842](https://github.com/browserbase/stagehand/pull/842) [`8bcb5d7`](https://github.com/browserbase/stagehand/commit/8bcb5d77debf6bf7601fd5c090efd7fde75c5d5e) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - improved handling for OS level dropdowns
+
+- [#846](https://github.com/browserbase/stagehand/pull/846) [`7bf10c5`](https://github.com/browserbase/stagehand/commit/7bf10c55b267078fe847c1d7f7a60d604f9c7c94) Thanks [@miguelg719](https://github.com/miguelg719)! - Filter attaching to target worker / shared_worker
+
+## 2.4.0
+
+### Minor Changes
+
+- [#819](https://github.com/browserbase/stagehand/pull/819) [`6a18c1e`](https://github.com/browserbase/stagehand/commit/6a18c1ee1e46d55c6e90c4d5572e17ed8daa140c) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - try playwright click and fall back to JS click event
+
+### Patch Changes
+
+- [#826](https://github.com/browserbase/stagehand/pull/826) [`124e0d3`](https://github.com/browserbase/stagehand/commit/124e0d3bb54ddb6738ede6d7aa99a945ef1cacd1) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix issue where we are unable to take actions on text nodes
+
+- [#818](https://github.com/browserbase/stagehand/pull/818) [`1660751`](https://github.com/browserbase/stagehand/commit/1660751cd14cb5b27d44f8167216afb8d1c3c45c) Thanks [@miguelg719](https://github.com/miguelg719)! - Added CUA support for Claude 4 models
+
+- [#821](https://github.com/browserbase/stagehand/pull/821) [`cadac9d`](https://github.com/browserbase/stagehand/commit/cadac9da09123d12e5d496a0e8b12660964c1b33) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - use playwright instead of playwright test
+
+- [#832](https://github.com/browserbase/stagehand/pull/832) [`759da55`](https://github.com/browserbase/stagehand/commit/759da55775eb2df81d56ae18c0f386fd9b02a9f0) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix \_refreshPageFromAPI to use parametrized apiKey
+
+- [#810](https://github.com/browserbase/stagehand/pull/810) [`a175a51`](https://github.com/browserbase/stagehand/commit/a175a519b8c14300db6f1ed30709e113d18e99db) Thanks [@miguelg719](https://github.com/miguelg719)! - Update logos
+
+- [#822](https://github.com/browserbase/stagehand/pull/822) [`8527a80`](https://github.com/browserbase/stagehand/commit/8527a80522c3eedb9516a6caa1a0e4e4be981a3d) Thanks [@miguelg719](https://github.com/miguelg719)! - Add model with date tag for OpenAI CUA
+
+- [#833](https://github.com/browserbase/stagehand/pull/833) [`55fca2f`](https://github.com/browserbase/stagehand/commit/55fca2f7da63cc0ef6e27b45a33f63c666cdce7e) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - adjust stagehandLogger.warn() level to be 1 instead of 0
+
+## 2.3.1
+
+### Patch Changes
+
+- [#796](https://github.com/browserbase/stagehand/pull/796) [`12a99b3`](https://github.com/browserbase/stagehand/commit/12a99b398d8a4c3eea3ca69a3cf793faaaf4aea3) Thanks [@miguelg719](https://github.com/miguelg719)! - Added a experimental flag to enable the newest and most experimental features
+
+- [#807](https://github.com/browserbase/stagehand/pull/807) [`2451797`](https://github.com/browserbase/stagehand/commit/2451797f64c0efa4a72fd70265110003c8d0a6cd) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - include version number in StagehandDefaultError message
+
+- [#803](https://github.com/browserbase/stagehand/pull/803) [`1d631a5`](https://github.com/browserbase/stagehand/commit/1d631a57a197390f672b718ae5199991ab27cfb1) Thanks [@miguelg719](https://github.com/miguelg719)! - Enable session affinity for cache optimization
+
+- [#804](https://github.com/browserbase/stagehand/pull/804) [`9c398bb`](https://github.com/browserbase/stagehand/commit/9c398bb9ec2d10bdb53ad5aa7e3b58cce24fdb2b) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - update operatorResponseSchema based on new openai spec
+
+- [#786](https://github.com/browserbase/stagehand/pull/786) [`c19ad7f`](https://github.com/browserbase/stagehand/commit/c19ad7f1e082e91fdeaa9c2ef63767a5a2b3a195) Thanks [@miguelg719](https://github.com/miguelg719)! - Handle reroute to account for rollout
+
+## 2.3.0
+
+### Minor Changes
+
+- [#737](https://github.com/browserbase/stagehand/pull/737) [`6ef6073`](https://github.com/browserbase/stagehand/commit/6ef60730cab0ad9025f44b6eeb2c83751d1dcd35) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - deprecate useTextExtract and remove functionality
+
+### Patch Changes
+
+- [#741](https://github.com/browserbase/stagehand/pull/741) [`5680d25`](https://github.com/browserbase/stagehand/commit/5680d2509352c383ad502c9f4fabde01fa638833) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - use safeparse for zod validation
+
+- [#783](https://github.com/browserbase/stagehand/pull/783) [`4de92a8`](https://github.com/browserbase/stagehand/commit/4de92a8af461fc95063faf39feee1d49259f58ba) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix the readme logo link
+
+## 2.2.1
+
+### Patch Changes
+
+- [#721](https://github.com/browserbase/stagehand/pull/721) [`be8652e`](https://github.com/browserbase/stagehand/commit/be8652e770b57fdb3299fa0b2efa4eb0e816434e) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix stagehand.close() functionality to include calling browser.close()
+
+- [#724](https://github.com/browserbase/stagehand/pull/724) [`6b413b7`](https://github.com/browserbase/stagehand/commit/6b413b7ad00b13ca0bd53ee2e7393023821408b6) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - rm refine step in extract
+
+- [#712](https://github.com/browserbase/stagehand/pull/712) [`7eafbd9`](https://github.com/browserbase/stagehand/commit/7eafbd9b1a73b37effa444929767df7c592caf02) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - deprecated `onlyVisible` param and remove its functionality
+
+- [#725](https://github.com/browserbase/stagehand/pull/725) [`1b50aa6`](https://github.com/browserbase/stagehand/commit/1b50aa61cf0a429dd6cb2760a08f7f698a50454b) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - dont overwrite .describe() when user defines a zod schema with z.string().url().describe()
+
+- [#717](https://github.com/browserbase/stagehand/pull/717) [`f2b7f1f`](https://github.com/browserbase/stagehand/commit/f2b7f1f284eef1f96753319b66c7d0b273a6f8cd) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - don't publish uncompiled ts to npm
+
+- [#719](https://github.com/browserbase/stagehand/pull/719) [`c8d672f`](https://github.com/browserbase/stagehand/commit/c8d672f7c410c256defbc2e87ead99239837aa28) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix `Invalid schema for response_format` error when extracting links
+
+- [#722](https://github.com/browserbase/stagehand/pull/722) [`bebf204`](https://github.com/browserbase/stagehand/commit/bebf2044502333c694743078c5b0c9deae11fb79) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - replace NBSP with regular space & remove special characters from dom+a11y tree
+
+- [#714](https://github.com/browserbase/stagehand/pull/714) [`37d6810`](https://github.com/browserbase/stagehand/commit/37d6810a704773d0383a86f98f5f17c7d5b21975) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix the native AI SDK client implementation to optionally take in an API key
+
+## 2.2.0
+
+### Minor Changes
+
+- [#655](https://github.com/browserbase/stagehand/pull/655) [`8814af9`](https://github.com/browserbase/stagehand/commit/8814af9ece99fddc3dd9fb32671d0513a3a00c67) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - extract links
+
+- [#675](https://github.com/browserbase/stagehand/pull/675) [`35c55eb`](https://github.com/browserbase/stagehand/commit/35c55ebf6c2867801a0a6f6988a883c8cb90cf9a) Thanks [@tkattkat](https://github.com/tkattkat)! - Added Gemini 2.5 Flash to Google supported models
+
+- [#668](https://github.com/browserbase/stagehand/pull/668) [`5c6d2cf`](https://github.com/browserbase/stagehand/commit/5c6d2cf89c9fbf198485506ed9ed75e07aec5cd4) Thanks [@miguelg719](https://github.com/miguelg719)! - Added a new class - Stagehand Evaluator - that wraps around a Stagehand object to determine whether a task is successful or not. Currently used for agent evals
+
+### Patch Changes
+
+- [#706](https://github.com/browserbase/stagehand/pull/706) [`18ac6fb`](https://github.com/browserbase/stagehand/commit/18ac6fba30f45b7557cecb890f4e84c75de8383c) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - remove unused fillInVariables fn
+
+- [#692](https://github.com/browserbase/stagehand/pull/692) [`6b95248`](https://github.com/browserbase/stagehand/commit/6b95248d6e02e5304ce4dd60499e31fc42af57eb) Thanks [@miguelg719](https://github.com/miguelg719)! - Updated the list of OpenAI models (4.1, o3...)
+
+- [#688](https://github.com/browserbase/stagehand/pull/688) [`7d81b3c`](https://github.com/browserbase/stagehand/commit/7d81b3c951c1f3dfc46845aefcc26ff175299bca) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - wrap page.evaluate to make sure we have injected browser side scripts before calling them
+
+- [#664](https://github.com/browserbase/stagehand/pull/664) [`b5ca00a`](https://github.com/browserbase/stagehand/commit/b5ca00a25ad0c33a5f4d3198e1bc59edb9956e7c) Thanks [@miguelg719](https://github.com/miguelg719)! - remove unnecessary log
+
+- [#683](https://github.com/browserbase/stagehand/pull/683) [`8f0f97b`](https://github.com/browserbase/stagehand/commit/8f0f97bc491e23ff0078c802aaf509fd04173c37) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - use javsacript click instead of playwright
+
+- [#705](https://github.com/browserbase/stagehand/pull/705) [`346ef5d`](https://github.com/browserbase/stagehand/commit/346ef5d0132dc1418dac18d26640a8df0435af57) Thanks [@miguelg719](https://github.com/miguelg719)! - Fixed removing a hanging observation map that is no longer used
+
+- [#698](https://github.com/browserbase/stagehand/pull/698) [`c145bc1`](https://github.com/browserbase/stagehand/commit/c145bc1d90ffd0d71c412de3af1c26c121e0b101) Thanks [@sameelarif](https://github.com/sameelarif)! - Fixing LLM client support to natively integrate with AI SDK
+
+- [#687](https://github.com/browserbase/stagehand/pull/687) [`edd6d3f`](https://github.com/browserbase/stagehand/commit/edd6d3feb47aac9f312a5edad78bf850ae1541db) Thanks [@miguelg719](https://github.com/miguelg719)! - Fixed the schema input for Gemini's response model
+
+- [#678](https://github.com/browserbase/stagehand/pull/678) [`5ec43d8`](https://github.com/browserbase/stagehand/commit/5ec43d8b9568c0f86b3e24bd83d1826c837656ed) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - allow form filling when form is not top-most element
+
+- [#694](https://github.com/browserbase/stagehand/pull/694) [`b8cc164`](https://github.com/browserbase/stagehand/commit/b8cc16405b712064a54c8cd591750368a47f35ea) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add telemetry for cua agents to stagehand.metrics
+
+- [#699](https://github.com/browserbase/stagehand/pull/699) [`d9f4243`](https://github.com/browserbase/stagehand/commit/d9f4243f6a8c8d4f3003ad6589f7eb4da6d23d0f) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - rm deprecated primitives from stagehand object
+
+- [#710](https://github.com/browserbase/stagehand/pull/710) [`9f4ab76`](https://github.com/browserbase/stagehand/commit/9f4ab76a0c1f0c2171290765c48c3bcea5b50e0f) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - support targeted extract for domExtract
+
+- [#677](https://github.com/browserbase/stagehand/pull/677) [`bc5a731`](https://github.com/browserbase/stagehand/commit/bc5a731241f7f4c5040dd672d8e3787555766421) Thanks [@miguelg719](https://github.com/miguelg719)! - Fixes a redundant unnecessary log
+
## 2.1.0
### Minor Changes
diff --git a/README.md b/README.md
index 788d6c073..56dda5f6d 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,29 @@
-
-
+
-
- The production-ready framework for AI browser automations.
+ The AI Browser Automation Framework
Read the Docs
-
-
+
+
-
+
-
-
+
+
@@ -33,81 +32,90 @@
+
+
+
+
+
+
+
+If you're looking for the Python implementation, you can find it
+ here
+
+
+
+
+## What is Stagehand?
+
+Stagehand is a browser automation framework used to control web browsers with natural language and code. By combining the power of AI with the precision of code, Stagehand makes web automation flexible, maintainable, and actually reliable.
+
## Why Stagehand?
-Most existing browser automation tools either require you to write low-level code in a framework like Selenium, Playwright, or Puppeteer, or use high-level agents that can be unpredictable in production. By letting developers choose what to write in code vs. natural language, Stagehand is the natural choice for browser automations in production.
+Most existing browser automation tools either require you to write low-level code in a framework like Selenium, Playwright, or Puppeteer, or use high-level agents that can be unpredictable in production. By letting developers choose what to write in code vs. natural language (and bridging the gap between the two) Stagehand is the natural choice for browser automations in production.
+
+1. **Choose when to write code vs. natural language**: use AI when you want to navigate unfamiliar pages, and use code when you know exactly what you want to do.
+
+2. **Go from AI-driven to repeatable workflows**: Stagehand lets you preview AI actions before running them, and also helps you easily cache repeatable actions to save time and tokens.
-1. **Choose when to write code vs. natural language**: use AI when you want to navigate unfamiliar pages, and use code ([Playwright](https://playwright.dev/)) when you know exactly what you want to do.
+3. **Write once, run forever**: Stagehand's auto-caching combined with self-healing remembers previous actions, runs without LLM inference, and knows when to involve AI whenever the website changes and your automation breaks.
-2. **Preview and cache actions**: Stagehand lets you preview AI actions before running them, and also helps you easily cache repeatable actions to save time and tokens.
+## Getting Started
+
+Start with Stagehand with one line of code, or check out our [Quickstart Guide](https://docs.stagehand.dev/v3/first-steps/quickstart) for more information:
-3. **Computer use models with one line of code**: Stagehand lets you integrate SOTA computer use models from OpenAI and Anthropic into the browser with one line of code.
+```bash
+npx create-browser-app
+```
## Example
Here's how to build a sample browser automation with Stagehand:
-
-
-
-
-
-
```typescript
-// Use Playwright functions on the page object
-const page = stagehand.page;
+// Stagehand's CDP engine provides an optimized, low level interface to the browser built for automation
+const page = stagehand.context.pages()[0];
await page.goto("https://github.com/browserbase");
// Use act() to execute individual actions
-await page.act("click on the stagehand repo");
+await stagehand.act("click on the stagehand repo");
-// Use Computer Use agents for larger actions
-const agent = stagehand.agent({
- provider: "openai",
- model: "computer-use-preview",
-});
+// Use agent() for multi-step tasks
+const agent = stagehand.agent();
await agent.execute("Get to the latest PR");
-// Use extract() to read data from the page
-const { author, title } = await page.extract({
- instruction: "extract the author and title of the PR",
- schema: z.object({
+// Use extract() to get structured data from the page
+const { author, title } = await stagehand.extract(
+ "extract the author and title of the PR",
+ z.object({
author: z.string().describe("The username of the PR author"),
title: z.string().describe("The title of the PR"),
}),
-});
+);
```
## Documentation
Visit [docs.stagehand.dev](https://docs.stagehand.dev) to view the full documentation.
-## Getting Started
-
-Start with Stagehand with one line of code, or check out our [Quickstart Guide](https://docs.stagehand.dev/get_started/quickstart) for more information:
-
-```bash
-npx create-browser-app
-```
-
-
### Build and Run from Source
```bash
git clone https://github.com/browserbase/stagehand.git
cd stagehand
-npm install
-npx playwright install
-npm run build
-npm run example # run the blank script at ./examples/example.ts
+pnpm install
+pnpm run build
+pnpm run example # run the blank script at ./examples/example.ts
```
Stagehand is best when you have an API key for an LLM provider and Browserbase credentials. To add these to your project, run:
@@ -117,26 +125,35 @@ cp .env.example .env
nano .env # Edit the .env file to add API keys
```
+### Installing from a branch
+
+You can install and build Stagehand directly from a github branch using [gitpkg](https://github.com/EqualMa/gitpkg)
+
+In your project's `package.json` set:
+```json
+"@browserbasehq/stagehand": "https://gitpkg.now.sh/browserbase/stagehand/packages/core?",
+```
+
+
## Contributing
-> [!NOTE]
-> We highly value contributions to Stagehand! For questions or support, please join our [Slack community](https://stagehand.dev/slack).
+> [!NOTE]
+> We highly value contributions to Stagehand! For questions or support, please join our [Discord community](https://stagehand.dev/discord).
-At a high level, we're focused on improving reliability, speed, and cost in that order of priority. If you're interested in contributing, we strongly recommend reaching out to [Anirudh Kamath](https://x.com/kamathematic) or [Paul Klein](https://x.com/pk_iv) in our [Slack community](https://stagehand.dev/slack) before starting to ensure that your contribution aligns with our goals.
+At a high level, we're focused on improving reliability, extensibility, speed, and cost in that order of priority. If you're interested in contributing, **bug fixes and small improvements are the best way to get started**. For more involved features, we strongly recommend reaching out to [Miguel Gonzalez](https://x.com/miguel_gonzf) or [Paul Klein](https://x.com/pk_iv) in our [Discord community](https://stagehand.dev/discord) before starting to ensure that your contribution aligns with our goals.
-For more information, please see our [Contributing Guide](https://docs.stagehand.dev/contributions/contributing).
+
## Acknowledgements
-This project heavily relies on [Playwright](https://playwright.dev/) as a resilient backbone to automate the web. It also would not be possible without the awesome techniques and discoveries made by [tarsier](https://github.com/reworkd/tarsier), and [fuji-web](https://github.com/normal-computing/fuji-web).
-
We'd like to thank the following people for their major contributions to Stagehand:
- [Paul Klein](https://github.com/pkiv)
-- [Anirudh Kamath](https://github.com/kamath)
- [Sean McGuire](https://github.com/seanmcguire12)
- [Miguel Gonzalez](https://github.com/miguelg719)
- [Sameel Arif](https://github.com/sameelarif)
+- [Thomas Katwan](https://github.com/tkattkat)
- [Filip Michalsky](https://github.com/filip-michalsky)
+- [Anirudh Kamath](https://github.com/kamath)
- [Jeremy Press](https://x.com/jeremypress)
- [Navid Pour](https://github.com/navidpour)
diff --git a/claude.md b/claude.md
new file mode 100644
index 000000000..03e3fed37
--- /dev/null
+++ b/claude.md
@@ -0,0 +1,297 @@
+# Stagehand Project
+
+This is a project that uses Stagehand V3, a browser automation framework with AI-powered `act`, `extract`, `observe`, and `agent` methods.
+
+The main class can be imported as `Stagehand` from `@browserbasehq/stagehand`.
+
+**Key Classes:**
+
+- `Stagehand`: Main orchestrator class providing `act`, `extract`, `observe`, and `agent` methods
+- `context`: A `V3Context` object that manages browser contexts and pages
+- `page`: Individual page objects accessed via `stagehand.context.pages()[i]` or created with `stagehand.context.newPage()`
+
+## Initialize
+
+```typescript
+import { Stagehand } from "@browserbasehq/stagehand";
+
+const stagehand = new Stagehand({
+ env: "LOCAL", // or "BROWSERBASE"
+ verbose: 2, // 0, 1, or 2
+ model: "openai/gpt-4.1-mini", // or any supported model
+});
+
+await stagehand.init();
+
+// Access the browser context and pages
+const page = stagehand.context.pages()[0];
+const context = stagehand.context;
+
+// Create new pages if needed
+const page2 = await stagehand.context.newPage();
+```
+
+## Act
+
+Actions are called on the `stagehand` instance (not the page). Use atomic, specific instructions:
+
+```typescript
+// Act on the current active page
+await stagehand.act("click the sign in button");
+
+// Act on a specific page (when you need to target a page that isn't currently active)
+await stagehand.act("click the sign in button", { page: page2 });
+```
+
+**Important:** Act instructions should be atomic and specific:
+
+- ✅ Good: "Click the sign in button" or "Type 'hello' into the search input"
+- ❌ Bad: "Order me pizza" or "Type in the search bar and hit enter" (multi-step)
+
+### Observe + Act Pattern (Recommended)
+
+Cache the results of `observe` to avoid unexpected DOM changes:
+
+```typescript
+const instruction = "Click the sign in button";
+
+// Get candidate actions
+const actions = await stagehand.observe(instruction);
+
+// Execute the first action
+await stagehand.act(actions[0]);
+```
+
+To target a specific page:
+
+```typescript
+const actions = await stagehand.observe("select blue as the favorite color", {
+ page: page2,
+});
+await stagehand.act(actions[0], { page: page2 });
+```
+
+## Extract
+
+Extract data from pages using natural language instructions. The `extract` method is called on the `stagehand` instance.
+
+### Basic Extraction (with schema)
+
+```typescript
+import { z } from "zod/v3";
+
+// Extract with explicit schema
+const data = await stagehand.extract(
+ "extract all apartment listings with prices and addresses",
+ z.object({
+ listings: z.array(
+ z.object({
+ price: z.string(),
+ address: z.string(),
+ }),
+ ),
+ }),
+);
+
+console.log(data.listings);
+```
+
+### Simple Extraction (without schema)
+
+```typescript
+// Extract returns a default object with 'extraction' field
+const result = await stagehand.extract("extract the sign in button text");
+
+console.log(result);
+// Output: { extraction: "Sign in" }
+
+// Or destructure directly
+const { extraction } = await stagehand.extract(
+ "extract the sign in button text",
+);
+console.log(extraction); // "Sign in"
+```
+
+### Targeted Extraction
+
+Extract data from a specific element using a selector:
+
+```typescript
+const reason = await stagehand.extract(
+ "extract the reason why script injection fails",
+ z.string(),
+ { selector: "/html/body/div[2]/div[3]/iframe/html/body/p[2]" },
+);
+```
+
+### URL Extraction
+
+When extracting links or URLs, use `z.string().url()`:
+
+```typescript
+const { links } = await stagehand.extract(
+ "extract all navigation links",
+ z.object({
+ links: z.array(z.string().url()),
+ }),
+);
+```
+
+### Extracting from a Specific Page
+
+```typescript
+// Extract from a specific page (when you need to target a page that isn't currently active)
+const data = await stagehand.extract(
+ "extract the placeholder text on the name field",
+ { page: page2 },
+);
+```
+
+## Observe
+
+Plan actions before executing them. Returns an array of candidate actions:
+
+```typescript
+// Get candidate actions on the current active page
+const [action] = await stagehand.observe("Click the sign in button");
+
+// Execute the action
+await stagehand.act(action);
+```
+
+Observing on a specific page:
+
+```typescript
+// Target a specific page (when you need to target a page that isn't currently active)
+const actions = await stagehand.observe("find the next page button", {
+ page: page2,
+});
+await stagehand.act(actions[0], { page: page2 });
+```
+
+## Agent
+
+Use the `agent` method to autonomously execute complex, multi-step tasks.
+
+### Basic Agent Usage
+
+```typescript
+const page = stagehand.context.pages()[0];
+await page.goto("https://www.google.com");
+
+const agent = stagehand.agent({
+ model: "google/gemini-2.0-flash",
+ executionModel: "google/gemini-2.0-flash",
+});
+
+const result = await agent.execute({
+ instruction: "Search for the stock price of NVDA",
+ maxSteps: 20,
+});
+
+console.log(result.message);
+```
+
+### Computer Use Agent (CUA)
+
+For more advanced scenarios using computer-use models:
+
+```typescript
+const agent = stagehand.agent({
+ mode: "cua", // Enable Computer Use Agent mode
+ model: "anthropic/claude-sonnet-4-20250514",
+ // or "google/gemini-2.5-computer-use-preview-10-2025"
+ systemPrompt: `You are a helpful assistant that can use a web browser.
+ Do not ask follow up questions, the user will trust your judgement.`,
+});
+
+await agent.execute({
+ instruction: "Apply for a library card at the San Francisco Public Library",
+ maxSteps: 30,
+});
+```
+
+### Agent with Custom Model Configuration
+
+```typescript
+const agent = stagehand.agent({
+ model: {
+ modelName: "google/gemini-2.5-computer-use-preview-10-2025",
+ apiKey: process.env.GEMINI_API_KEY,
+ },
+ systemPrompt: `You are a helpful assistant.`,
+});
+```
+
+### Agent with Integrations (MCP/External Tools)
+
+```typescript
+const agent = stagehand.agent({
+ integrations: [`https://mcp.exa.ai/mcp?exaApiKey=${process.env.EXA_API_KEY}`],
+ systemPrompt: `You have access to the Exa search tool.`,
+});
+```
+
+### Agent Hybrid Mode
+
+Hybrid mode uses both DOM-based and coordinate-based tools (act, click, type, dragAndDrop) for visual interactions. This requires `experimental: true` and models that support reliable coordinate-based actions.
+
+**Recommended models for hybrid mode:**
+
+- `google/gemini-3-flash-preview`
+- `anthropic/claude-sonnet-4-20250514`, `anthropic/claude-sonnet-4-5-20250929`, `anthropic/claude-haiku-4-5-20251001`
+
+```typescript
+const stagehand = new Stagehand({
+ env: "LOCAL",
+ experimental: true, // Required for hybrid mode
+});
+await stagehand.init();
+
+const agent = stagehand.agent({
+ mode: "hybrid",
+ model: "google/gemini-3-flash-preview",
+});
+
+await agent.execute({
+ instruction: "Click the submit button and fill the form",
+ maxSteps: 20,
+ highlightCursor: true, // Enabled by default in hybrid mode
+});
+```
+
+**Agent modes:**
+
+- `"dom"` (default): Uses DOM-based tools (act, fillForm) - works with any model
+- `"hybrid"`: Uses both DOM-based and coordinate-based tools (act, click, type, dragAndDrop) - requires grounding-capable models
+- `"cua"`: Uses Computer Use Agent providers
+
+## Advanced Features
+
+### DeepLocator (XPath Targeting)
+
+Target specific elements across shadow DOM and iframes:
+
+```typescript
+await page
+ .deepLocator("/html/body/div[2]/div[3]/iframe/html/body/p")
+ .highlight({
+ durationMs: 5000,
+ contentColor: { r: 255, g: 0, b: 0 },
+ });
+```
+
+### Multi-Page Workflows
+
+```typescript
+const page1 = stagehand.context.pages()[0];
+await page1.goto("https://example.com");
+
+const page2 = await stagehand.context.newPage();
+await page2.goto("https://example2.com");
+
+// Act/extract/observe operate on the current active page by default
+// Pass { page } option to target a specific page
+await stagehand.act("click button", { page: page1 });
+await stagehand.extract("get title", { page: page2 });
+```
diff --git a/docs/logging.md b/docs/logging.md
deleted file mode 100644
index fa73482c1..000000000
--- a/docs/logging.md
+++ /dev/null
@@ -1,184 +0,0 @@
-# Stagehand Logging System
-
-The Stagehand logging system uses [Pino](https://getpino.io/) to provide structured, efficient, and configurable logging.
-
-## Log Levels
-
-Stagehand uses three primary log levels:
-
-| Level | Name | Description |
-| ----- | ----- | --------------------------------------- |
-| 0 | error | Critical errors and important warnings |
-| 1 | info | Standard information messages (default) |
-| 2 | debug | Detailed information for debugging |
-
-The verbosity of logging is controlled by the `verbose` option when creating a Stagehand instance:
-
-```typescript
-const stagehand = new Stagehand({
- verbose: 2, // Show all logs up to debug level
- // other options...
-});
-```
-
-## Using the Logger
-
-The logging system is automatically initialized with your Stagehand instance. You can access it directly via:
-
-```typescript
-// Log an error
-stagehand.log({
- message: "An error occurred",
- level: 0,
- category: "error",
-});
-
-// Log info (level 1 is default)
-stagehand.log({
- message: "Operation completed",
- category: "operation",
-});
-
-// Log debug information
-stagehand.log({
- message: "Debug details",
- level: 2,
- category: "debug",
- auxiliary: {
- details: {
- value: JSON.stringify({ key: "value" }),
- type: "object",
- },
- },
-});
-```
-
-## Inference Logging
-
-For detailed logging of inference operations (act, extract, observe), Stagehand provides specialized logging:
-
-```typescript
-// Enable inference logging to file
-const stagehand = new Stagehand({
- logInferenceToFile: true,
- // other options...
-});
-```
-
-When enabled, inference logs are written to the `inference_summary` directory in your project.
-
-## Pretty Printing
-
-By default, logs in development are formatted with colors and readable timestamps using Pino's pretty formatting. For production environments or when sending logs to external systems, you can disable pretty printing.
-
-## Customizing Logging
-
-### Using Your Own Logger
-
-You can provide your own custom logger when creating a Stagehand instance:
-
-```typescript
-const stagehand = new Stagehand({
- logger: (logLine) => {
- // Your custom logging logic here
- console.log(`[${logLine.category}] ${logLine.message}`);
- },
- // other options...
-});
-```
-
-When you provide a custom logger, Stagehand will automatically disable its internal Pino logger to prevent duplicate logging. Your logger will receive all log events directly.
-
-### Configuring Pino
-
-If you want to use Pino but with custom configuration:
-
-```typescript
-import { StagehandLogger } from "@browserbasehq/stagehand/lib/logger";
-
-// Create a custom configured logger
-const customLogger = new StagehandLogger({
- pretty: true,
- level: "debug",
- // Other Pino options...
-});
-
-// Pass it to Stagehand
-const stagehand = new Stagehand({
- logger: (logLine) => customLogger.log(logLine),
- // other options...
-});
-```
-
-## Advanced Usage
-
-### Creating a New StagehandLogger Instance
-
-You can create a standalone logger for use in your application:
-
-```typescript
-import { StagehandLogger } from "@browserbasehq/stagehand/lib/logger";
-
-const logger = new StagehandLogger({
- pretty: true,
- level: "debug",
-});
-
-logger.info("Information message");
-logger.debug("Debug message", { details: "some data" });
-logger.error("Error message", { error: "details" });
-```
-
-### Configuring Log Output
-
-You can direct logs to a file or other destination:
-
-```typescript
-import fs from "fs";
-import { StagehandLogger } from "@browserbasehq/stagehand/lib/logger";
-
-const fileStream = fs.createWriteStream("./logs/application.log", {
- flags: "a",
-});
-
-const logger = new StagehandLogger({
- destination: fileStream,
-});
-```
-
-### Disabling Pino Explicitly
-
-If you want to handle all logging yourself without using Pino:
-
-```typescript
-import { StagehandLogger } from "@browserbasehq/stagehand/lib/logger";
-
-const logger = new StagehandLogger(
- {
- usePino: false,
- },
- (logLine) => {
- // Your custom logging logic
- console.log(`[${logLine.level}] ${logLine.message}`);
- },
-);
-```
-
-## Troubleshooting
-
-If you're not seeing logs:
-
-1. Check your `verbose` setting - it may be too low for the log levels you're trying to see
-2. Verify that your log messages have the correct level set
-3. If using a custom logger, ensure it's correctly handling the log messages
-
-If you're seeing duplicate logs:
-
-1. Make sure you're not creating multiple instances of StagehandLogger that log to the same output
-2. If providing a custom logger to Stagehand, it will automatically disable the internal Pino logger
-
-If logs are not being written to files:
-
-1. Ensure you have write permissions to the target directory
-2. Check that the `logInferenceToFile` option is enabled
-3. Verify that the destination path exists or can be created
diff --git a/docs/release.md b/docs/release.md
deleted file mode 100644
index 2a0402a1a..000000000
--- a/docs/release.md
+++ /dev/null
@@ -1,117 +0,0 @@
-# Releasing
-
-We use [Changesets](https://github.com/changesets/changesets) to version and release our packages.
-
-When we merge to main, the release workflow will:
-
-1. Create a release pull request with:
- - A version bump for the package calculated by the changesets.
- - A changelog entry summarizing the changes in the release.
-1. Create an `alpha` version of the package with whatever is merged to main, and you can install it with `npm install @browserbasehq/stagehand@alpha`. This is useful for testing the release before it's published to the `latest` tag.
-
-When the pull request is merged, the release workflow will publish the package to npm with the version calculated by the changesets.
-
-For more information on how changesets work, see the [changesets docs](https://github.com/changesets/changesets) and our [release.yml file](/.github/workflows/release.yml).
-
-# Manually Releasing
-
-> [!WARNING]
-> You should not need to manually release unless absolutely necessary. Our automated release workflow handles this for you when changes are merged to main.
-
-When you're ready to cut a release, start by versioning the packages:
-
-```
-npx changeset version
-```
-
-This will consume the changesets in [`.changeset`](../.changeset) and update the [changelog](../CHANGELOG.md) and [`package.json`](../package.json):
-
-```
-% git status --short
- M CHANGELOG.md
- M package.json
-```
-
-Based on the versions implications declared by the changesets, the package version will be updated to the next patch, minor, or major:
-
-```diff
- "name": "@browserbasehq/stagehand",
-- "version": "1.3.0",
-+ "version": "1.3.1",
-```
-
-Since we updated the `package.json`, we should also update the lockfile ([`package-lock.json`](../package-lock.json)) for tidiness:
-
-```
-npm install
-```
-
-Now the lockfile should be updated:
-
-```
-% git status --short
- M CHANGELOG.md
- M package-lock.json
- M package.json
-```
-
-The diff will look something like this:
-
-```diff
- {
- "name": "@browserbasehq/stagehand",
-- "version": "1.3.0",
-+ "version": "1.3.1",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "@browserbasehq/stagehand",
-- "version": "1.3.0",
-+ "version": "1.3.1",
-```
-
-At this point we're ready to commit our changes.
-It's probably a good idea to have some consistency around the name of this commit message:
-
-```
-git commit -am 'Version Packages'
-```
-
-Ok, now it's time to publish the release.
-Before we do, we have to build the artifacts that comprise the tarball.
-Let's clean our working directory first so that we don't accidentally include anything in the tarball that shouldn't be there:
-
-```
-% git clean -fxd -e .env
-Removing dist/
-Removing lib/dom/build/
-Removing node_modules/
-```
-
-Let's reinstall dependencies and build the artifacts:
-
-```
-npm install && npm run build
-```
-
-Now we're ready to publish to NPM. You have to be logged in via the `npm` CLI and have to be part of the `@browserbasehq` org:
-
-```
-npx changeset publish
-```
-
-Congratulations! You just published a new version of `@browserbasehq/stagehand`. 🤘
-
-In the process of publishing, Changesets created an [annotated git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging):
-
-```
-🦋 Creating git tag...
-🦋 New tag: v1.3.1
-```
-
-Let's push the commit and tag to GitHub for posterity:
-
-```
-git push --follow-tags
-```
diff --git a/eslint.config.mjs b/eslint.config.mjs
index c0d192cde..6b45c9e88 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -6,7 +6,19 @@ import tseslint from "typescript-eslint";
export default [
{ files: ["**/*.{js,mjs,cjs,ts}"] },
{ languageOptions: { globals: globals.browser } },
- { ignores: ["**/dist/**", "lib/dom/build/**"] },
+ {
+ ignores: [
+ "**/dist/**",
+ "**/node_modules/**",
+ "packages/core/lib/dom/build/**",
+ "packages/core/lib/v3/dom/build/**",
+ "**/*.config.js",
+ "**/*.config.mjs",
+ ".browserbase/**",
+ "**/.browserbase/**",
+ "**/*.json",
+ ],
+ },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
];
diff --git a/evals/args.ts b/evals/args.ts
deleted file mode 100644
index 4c2f748fc..000000000
--- a/evals/args.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import process from "process";
-import { EvalCategorySchema } from "@/types/evals";
-
-const rawArgs = process.argv.slice(2);
-
-const parsedArgs: {
- env?: string;
- trials?: number;
- concurrency?: number;
- extractMethod?: string;
- provider?: string;
- leftover: string[];
-} = {
- leftover: [],
-};
-
-for (const arg of rawArgs) {
- if (arg.startsWith("env=")) {
- parsedArgs.env = arg.split("=")[1]?.toLowerCase();
- } else if (arg.startsWith("trials=")) {
- const val = parseInt(arg.split("=")[1], 10);
- if (!isNaN(val)) {
- parsedArgs.trials = val;
- }
- } else if (arg.startsWith("concurrency=")) {
- const val = parseInt(arg.split("=")[1], 10);
- if (!isNaN(val)) {
- parsedArgs.concurrency = val;
- }
- } else if (arg.startsWith("--extract-method=")) {
- parsedArgs.extractMethod = arg.split("=")[1];
- } else if (arg.startsWith("provider=")) {
- parsedArgs.provider = arg.split("=")[1]?.toLowerCase();
- } else {
- parsedArgs.leftover.push(arg);
- }
-}
-
-/** Apply environment defaults or overrides */
-if (parsedArgs.env === "browserbase") {
- process.env.EVAL_ENV = "BROWSERBASE";
-} else if (parsedArgs.env === "local") {
- process.env.EVAL_ENV = "LOCAL";
-}
-
-if (parsedArgs.trials !== undefined) {
- process.env.EVAL_TRIAL_COUNT = String(parsedArgs.trials);
-}
-if (parsedArgs.concurrency !== undefined) {
- process.env.EVAL_MAX_CONCURRENCY = String(parsedArgs.concurrency);
-}
-
-const extractMethod = parsedArgs.extractMethod || "domExtract";
-process.env.EXTRACT_METHOD = extractMethod;
-
-const useTextExtract = extractMethod === "textExtract";
-const useAccessibilityTree = extractMethod === "accessibilityTree";
-
-const DEFAULT_EVAL_CATEGORIES = process.env.EVAL_CATEGORIES
- ? process.env.EVAL_CATEGORIES.split(",")
- : [
- "observe",
- "act",
- "combination",
- "extract",
- "experimental",
- "text_extract",
- "targeted_extract",
- "regression_llm_providers",
- "regression",
- "llm_clients",
- ];
-
-// Finally, interpret leftover arguments to see if user typed "category X" or a single eval name
-let filterByCategory: string | null = null;
-let filterByEvalName: string | null = null;
-
-if (parsedArgs.leftover.length > 0) {
- if (parsedArgs.leftover[0].toLowerCase() === "category") {
- filterByCategory = parsedArgs.leftover[1];
- if (!filterByCategory) {
- console.error("Error: Category name not specified.");
- process.exit(1);
- }
- try {
- EvalCategorySchema.parse(filterByCategory);
- } catch {
- console.error(
- `Error: Invalid category "${filterByCategory}". Valid categories are: ${DEFAULT_EVAL_CATEGORIES.join(", ")}`,
- );
- process.exit(1);
- }
- } else {
- // If leftover[0] is not "category", interpret it as a task/eval name
- filterByEvalName = parsedArgs.leftover[0];
- }
-}
-
-if (parsedArgs.provider !== undefined) {
- process.env.EVAL_PROVIDER = parsedArgs.provider;
-}
-
-export {
- filterByCategory,
- filterByEvalName,
- useTextExtract,
- useAccessibilityTree,
- DEFAULT_EVAL_CATEGORIES,
- parsedArgs,
-};
diff --git a/evals/deterministic/auxiliary/logo.png b/evals/deterministic/auxiliary/logo.png
deleted file mode 100644
index 7ae5d4850..000000000
Binary files a/evals/deterministic/auxiliary/logo.png and /dev/null differ
diff --git a/evals/deterministic/bb.playwright.config.ts b/evals/deterministic/bb.playwright.config.ts
deleted file mode 100644
index cea358f55..000000000
--- a/evals/deterministic/bb.playwright.config.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { defineConfig, devices } from "@playwright/test";
-
-/**
- * See https://playwright.dev/docs/test-configuration.
- */
-export default defineConfig({
- testDir: "./tests/browserbase",
-
- /* Fail the build on CI if you accidentally left test.only in the source code. */
- /* Run tests in files in parallel */
- fullyParallel: true,
- /* Reporter to use. See https://playwright.dev/docs/test-reporters */
- // reporter: "html",
- reporter: "line",
- /* Retry on CI only */
- retries: 2,
-
- /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
- use: {
- /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
- trace: "on-first-retry",
- },
-
- /* Configure projects for major browsers */
- projects: [
- {
- name: "chromium",
- use: { ...devices["Desktop Chrome"] },
- },
- ],
-});
diff --git a/evals/deterministic/e2e.playwright.config.ts b/evals/deterministic/e2e.playwright.config.ts
deleted file mode 100644
index 4ed3c2594..000000000
--- a/evals/deterministic/e2e.playwright.config.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { defineConfig, devices } from "@playwright/test";
-
-/**
- * See https://playwright.dev/docs/test-configuration.
- */
-export default defineConfig({
- // Look in "tests" for test files...
- testDir: "./tests",
- // ...but ignore anything in "tests/browserbase & "tests/local"
- testIgnore: ["**/browserbase/**", "**/local/**"],
-
- /* Fail the build on CI if you accidentally left test.only in the source code. */
- /* Run tests in files in parallel */
- fullyParallel: true,
- /* Reporter to use. See https://playwright.dev/docs/test-reporters */
- // reporter: "html",
- reporter: "line",
- retries: 2,
-
- /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
- use: {
- /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
- trace: "on-first-retry",
- },
-
- /* Configure projects for major browsers */
- projects: [
- {
- name: "chromium",
- use: { ...devices["Desktop Chrome"] },
- },
- ],
-});
diff --git a/evals/deterministic/local.playwright.config.ts b/evals/deterministic/local.playwright.config.ts
deleted file mode 100644
index 3c309ff70..000000000
--- a/evals/deterministic/local.playwright.config.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { defineConfig, devices } from "@playwright/test";
-
-/**
- * See https://playwright.dev/docs/test-configuration.
- */
-export default defineConfig({
- testDir: "./tests/local",
-
- /* Maximum time one test can run for. */
- timeout: 30 * 1000,
-
- /* Fail the build on CI if you accidentally left test.only in the source code. */
- forbidOnly: !!process.env.CI,
-
- /* Run tests in files in parallel */
- fullyParallel: false,
-
- /* Reporter to use */
- reporter: "line",
-
- /* Retry on CI only */
- retries: process.env.CI ? 2 : 0,
-
- /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
- use: {
- /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
- trace: "on-first-retry",
- },
-
- /* Configure projects for major browsers */
- projects: [
- {
- name: "chromium",
- use: { ...devices["Desktop Chrome"] },
- },
- ],
-});
diff --git a/evals/deterministic/stagehand.config.ts b/evals/deterministic/stagehand.config.ts
deleted file mode 100644
index a7bcbb644..000000000
--- a/evals/deterministic/stagehand.config.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { default as DefaultStagehandConfig } from "@/stagehand.config";
-import type { ConstructorParams } from "@/dist";
-import dotenv from "dotenv";
-dotenv.config({ path: "../../.env" });
-
-const StagehandConfig: ConstructorParams = {
- ...DefaultStagehandConfig,
- env: "LOCAL" /* Environment to run Stagehand in */,
- verbose: 1 /* Logging verbosity level (0=quiet, 1=normal, 2=verbose) */,
- browserbaseSessionCreateParams: {
- projectId: process.env.BROWSERBASE_PROJECT_ID,
- },
- enableCaching: false /* Enable caching functionality */,
- localBrowserLaunchOptions: {
- headless: true /* Run browser in headless mode */,
- },
-};
-export default StagehandConfig;
diff --git a/evals/deterministic/tests/BrowserContext/addInitScript.test.ts b/evals/deterministic/tests/BrowserContext/addInitScript.test.ts
deleted file mode 100644
index cf78933c2..000000000
--- a/evals/deterministic/tests/BrowserContext/addInitScript.test.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandContext - addInitScript", () => {
- test("should inject a script on the context before pages load", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const context = stagehand.context;
-
- await context.addInitScript(() => {
- const w = window as typeof window & {
- __testContextScriptVar?: string;
- };
- w.__testContextScriptVar = "Hello from context.initScript!";
- });
-
- const pageA = await context.newPage();
- await pageA.goto("https://example.com");
-
- const resultA = await pageA.evaluate(() => {
- const w = window as typeof window & {
- __testContextScriptVar?: string;
- };
- return w.__testContextScriptVar;
- });
- expect(resultA).toBe("Hello from context.initScript!");
-
- const pageB = await context.newPage();
- await pageB.goto("https://docs.browserbase.com");
-
- const resultB = await pageB.evaluate(() => {
- const w = window as typeof window & {
- __testContextScriptVar?: string;
- };
- return w.__testContextScriptVar;
- });
- expect(resultB).toBe("Hello from context.initScript!");
-
- await stagehand.close();
- });
-});
diff --git a/evals/deterministic/tests/BrowserContext/cookies.test.ts b/evals/deterministic/tests/BrowserContext/cookies.test.ts
deleted file mode 100644
index b42c04507..000000000
--- a/evals/deterministic/tests/BrowserContext/cookies.test.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandContext - Cookies", () => {
- let stagehand: Stagehand;
-
- test.beforeEach(async () => {
- stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- });
-
- test.afterEach(async () => {
- await stagehand.close();
- });
-
- test("should add cookies and retrieve them", async () => {
- const context = stagehand.context; // This is the wrapped BrowserContext
- const url = "https://example.com";
-
- await context.addCookies([
- {
- name: "myCookie",
- value: "myValue",
- domain: "example.com",
- path: "/",
- expires: Math.floor(Date.now() / 1000) + 3600,
- httpOnly: false,
- secure: false,
- sameSite: "Lax",
- },
- ]);
-
- const cookies = await context.cookies(url);
- expect(cookies.length).toBeGreaterThan(0);
-
- const myCookie = cookies.find((c) => c.name === "myCookie");
- expect(myCookie).toBeDefined();
- expect(myCookie?.value).toBe("myValue");
- });
-
- test("should clear all cookies", async () => {
- const context = stagehand.context;
- const url = "https://example.com";
-
- await context.addCookies([
- {
- name: "myOtherCookie",
- value: "anotherValue",
- domain: "example.com",
- path: "/",
- expires: Math.floor(Date.now() / 1000) + 3600,
- httpOnly: false,
- secure: false,
- sameSite: "Lax",
- },
- ]);
-
- const cookiesBefore = await context.cookies(url);
- const found = cookiesBefore.some((c) => c.name === "myOtherCookie");
- expect(found).toBe(true);
-
- await context.clearCookies();
-
- const cookiesAfter = await context.cookies(url);
- const stillFound = cookiesAfter.some((c) => c.name === "myOtherCookie");
- expect(stillFound).toBe(false);
- });
-});
diff --git a/evals/deterministic/tests/BrowserContext/multiPage.test.ts b/evals/deterministic/tests/BrowserContext/multiPage.test.ts
deleted file mode 100644
index b0b38cdf0..000000000
--- a/evals/deterministic/tests/BrowserContext/multiPage.test.ts
+++ /dev/null
@@ -1,209 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-import { Page } from "@/dist";
-
-import http from "http";
-import express from "express";
-import { Server as WebSocketServer } from "ws";
-
-test.describe("StagehandContext - Multi-page Support", () => {
- let stagehand: Stagehand;
- let server: http.Server;
- let wss: WebSocketServer;
- let serverPort: number;
-
- test.beforeAll(async () => {
- // Set up a local Express server
- const app = express();
-
- // Serve test pages
- app.get("/page1", (_req, res) => {
- res.set("Content-Type", "text/html");
- res.end(`
-
- Page 1
-
- Page 1 Content
-
-
-
- `);
- });
-
- app.get("/page2", (_req, res) => {
- res.set("Content-Type", "text/html");
- res.end(`
-
- Page 2
-
- Page 2 Content
-
-
- `);
- });
-
- // Create the server on a random free port
- server = http.createServer(app);
- await new Promise((resolve) => {
- server.listen(0, () => resolve());
- });
- const address = server.address();
- if (typeof address === "object" && address !== null) {
- serverPort = address.port;
- } else {
- throw new Error("Failed to get server port");
- }
-
- // Set up WebSocket for future tests
- wss = new WebSocketServer({ server, path: "/socket" });
- wss.on("connection", (ws) => {
- console.log("WebSocket client connected");
- ws.send("Hello from server WebSocket");
- });
- });
-
- test.beforeEach(async () => {
- stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- });
-
- test.afterEach(async () => {
- await stagehand.close();
- });
-
- test.afterAll(async () => {
- wss?.close();
- server?.close();
- });
-
- /**
- * Test enhanced page capabilities
- */
- test("should provide enhanced capabilities for new pages", async () => {
- const context = stagehand.context;
- const newPage = await context.newPage();
-
- // Verify enhanced methods
- expect(typeof newPage.act).toBe("function");
- expect(typeof newPage.extract).toBe("function");
- expect(typeof newPage.observe).toBe("function");
-
- // Verify basic Playwright functionality
- expect(typeof newPage.goto).toBe("function");
- expect(typeof newPage.click).toBe("function");
-
- // Test navigation maintains capabilities
- await newPage.goto(`http://localhost:${serverPort}/page1`);
- expect(typeof newPage.act).toBe("function");
- expect(await newPage.title()).toBe("Page 1");
- });
-
- /**
- * Test context.pages() functionality
- */
- test("should return array of enhanced pages via context.pages()", async () => {
- const context = stagehand.context;
-
- // Create multiple pages
- const page1 = await context.newPage();
- const page2 = await context.newPage();
-
- await page1.goto(`http://localhost:${serverPort}/page1`);
- await page2.goto(`http://localhost:${serverPort}/page2`);
-
- const pages = context.pages();
- expect(pages).toContain(page1);
- expect(pages).toContain(page2);
-
- // Verify all pages have enhanced capabilities
- for (const page of pages) {
- expect(typeof page.act).toBe("function");
- expect(typeof page.extract).toBe("function");
- expect(typeof page.observe).toBe("function");
- }
- });
-
- /**
- * Test popup handling
- */
- test("should handle popups with enhanced capabilities", async () => {
- const mainPage = stagehand.page;
- let popupPage: Page | null = null;
-
- mainPage.on("popup", (page: Page) => {
- popupPage = page;
- });
-
- await mainPage.goto(`http://localhost:${serverPort}/page1`);
- await mainPage.click("#popupBtn");
-
- // Verify popup has enhanced capabilities
- expect(popupPage).not.toBeNull();
- expect(typeof popupPage.act).toBe("function");
- expect(typeof popupPage.extract).toBe("function");
- expect(typeof popupPage.observe).toBe("function");
-
- if (popupPage) {
- await popupPage.waitForLoadState();
- expect(await popupPage.title()).toBe("Page 2");
- }
- });
-
- /**
- * Test page tracking and cleanup
- */
- test("should properly track and cleanup pages", async () => {
- const context = stagehand.context;
- const initialPages = context.pages().length;
-
- const newPage = await context.newPage();
- await newPage.goto(`http://localhost:${serverPort}/page1`);
-
- expect(context.pages().length).toBe(initialPages + 1);
- await newPage.close();
- expect(context.pages().length).toBe(initialPages);
- });
-
- /**
- * Test enhanced methods across pages
- */
- test("should support enhanced methods across all pages", async () => {
- const page1 = await stagehand.context.newPage();
- const page2 = await stagehand.context.newPage();
-
- await page1.goto(`http://localhost:${serverPort}/page1`);
- await page2.goto(`http://localhost:${serverPort}/page2`);
-
- // Verify both pages have enhanced capabilities
- expect(typeof page1.act).toBe("function");
- expect(typeof page1.extract).toBe("function");
- expect(typeof page1.observe).toBe("function");
-
- expect(typeof page2.act).toBe("function");
- expect(typeof page2.extract).toBe("function");
- expect(typeof page2.observe).toBe("function");
- });
-
- /**
- * Test active page tracking
- */
- test("should update stagehand.page when creating new pages", async () => {
- const initialPage = stagehand.page;
-
- // Create a new page and verify it becomes active
- const newPage = await stagehand.context.newPage();
- expect(stagehand.page).toBe(newPage);
- expect(stagehand.page).not.toBe(initialPage);
-
- // Navigate and verify it's still the active page
- await newPage.goto(`http://localhost:${serverPort}/page1`);
- expect(stagehand.page).toBe(newPage);
- expect(await stagehand.page.title()).toBe("Page 1");
-
- // Create another page and verify it becomes active
- const anotherPage = await stagehand.context.newPage();
- expect(stagehand.page).toBe(anotherPage);
- expect(stagehand.page).not.toBe(newPage);
- });
-});
diff --git a/evals/deterministic/tests/BrowserContext/page.test.ts b/evals/deterministic/tests/BrowserContext/page.test.ts
deleted file mode 100644
index f6c7aca58..000000000
--- a/evals/deterministic/tests/BrowserContext/page.test.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-import http from "http";
-import express from "express";
-import { Server as WebSocketServer } from "ws";
-
-test.describe("StagehandContext - pages and newPage", () => {
- let stagehand: Stagehand;
- let server: http.Server;
- let wss: WebSocketServer;
- let serverPort: number;
-
- test.beforeAll(async () => {
- // 1. Spin up a local Express server
- const app = express();
-
- // Serve a single page at "/"
- app.get("/", (_req, res) => {
- res.set("Content-Type", "text/html");
- res.end(`
-
-
- Test Page
-
-
- Hello from local server
-
-
-
- `);
- });
-
- // Create the server on a random free port
- server = http.createServer(app);
- await new Promise((resolve) => {
- server.listen(0, () => resolve());
- });
- const address = server.address();
- if (typeof address === "object" && address !== null) {
- serverPort = address.port;
- } else {
- throw new Error("Failed to get server port");
- }
-
- // Optionally set up a WebSocket for future tests
- wss = new WebSocketServer({ server, path: "/socket" });
- wss.on("connection", (ws) => {
- console.log("WebSocket client connected");
- ws.send("Hello from server WebSocket");
- });
- });
-
- test.beforeEach(async () => {
- // 2. Create & init Stagehand for each test
- stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- });
-
- test.afterEach(async () => {
- await stagehand.close();
- });
-
- test.afterAll(async () => {
- // Shut down local server
- wss?.close();
- server?.close();
- });
-
- /**
- * Test context.newPage() and context.pages()
- */
- test("should create multiple pages and list them via context.pages()", async () => {
- const context = stagehand.context;
-
- // Create multiple pages
- const page1 = await context.newPage();
- const page2 = await context.newPage();
-
- // Confirm context.pages() sees them
- const allPages = context.pages();
-
- // We expect at least these 2 pages. If a default blank page existed, total might be more.
- // The key is that page1 & page2 are in the array:
- expect(allPages).toContain(page1);
- expect(allPages).toContain(page2);
-
- // Navigate page1 to the local server
- await page1.goto(`http://localhost:${serverPort}`);
- expect(await page1.title()).toBe("Test Page");
- });
-});
diff --git a/evals/deterministic/tests/BrowserContext/routing.test.ts b/evals/deterministic/tests/BrowserContext/routing.test.ts
deleted file mode 100644
index 1dd53664d..000000000
--- a/evals/deterministic/tests/BrowserContext/routing.test.ts
+++ /dev/null
@@ -1,236 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-import http from "http";
-import express from "express";
-import { Server as WebSocketServer } from "ws";
-import fs from "fs";
-import path from "path";
-
-const HAR_CONTENT = `{
- "log": {
- "version": "1.2",
- "creator": { "name": "PlaywrightTest", "version": "1.0" },
- "entries": [
- {
- "startedDateTime": "2023-01-01T00:00:00.000Z",
- "time": 5,
- "request": {
- "method": "GET",
- "url": "http://localhost/har-example.json",
- "httpVersion": "HTTP/1.1",
- "cookies": [],
- "headers": [],
- "queryString": [],
- "headersSize": -1,
- "bodySize": 0
- },
- "response": {
- "status": 200,
- "statusText": "OK",
- "httpVersion": "HTTP/1.1",
- "cookies": [],
- "headers": [{"name":"Content-Type","value":"application/json"}],
- "content": {
- "size": 27,
- "mimeType": "application/json",
- "text": "{\\"harKey\\":\\"harValue\\"}"
- },
- "redirectURL": "",
- "headersSize": -1,
- "bodySize": 0
- },
- "cache": {},
- "timings": { "send": 0, "wait": 5, "receive": 0 }
- }
- ]
- }
-}`;
-
-test.describe("StagehandContext - Routing APIs with dynamic setup", () => {
- let stagehand: Stagehand;
- let server: http.Server;
- let wss: WebSocketServer;
- let serverPort: number;
-
- test.beforeAll(async () => {
- const app = express();
-
- app.get("/example.json", (_req, res) => {
- res.json({ original: "server-data" });
- });
-
- app.get("/har-example.json", (_req, res) => {
- res.json({
- fromServer:
- "This should be replaced by HAR if routeFromHar is in effect",
- });
- });
-
- server = http.createServer(app);
- await new Promise((resolve) => {
- server.listen(0, () => resolve());
- });
- const address = server.address();
- if (typeof address === "object" && address !== null) {
- serverPort = address.port;
- } else {
- throw new Error("Failed to get server port");
- }
-
- // Set up a WebSocket endpoint at "/socket"
- wss = new WebSocketServer({ server, path: "/socket" });
- wss.on("connection", (ws) => {
- console.log("WebSocket client connected");
- ws.send("Hello from server WebSocket");
-
- // Echo messages back
- ws.on("message", (message) => {
- console.log("Server received WS message:", message);
- ws.send(`Server echo: ${message}`);
- });
- });
- });
-
- test.beforeEach(async () => {
- stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- });
-
- test.afterEach(async () => {
- await stagehand.close();
- });
-
- test.afterAll(async () => {
- wss?.close();
- server?.close();
- });
-
- test("should intercept requests, mock the response, handle websockets, and unroute them", async () => {
- const context = stagehand.context;
- const baseURL = `http://localhost:${serverPort}`;
-
- // 1. route: intercept "/example.json" and fulfill with a mock response
- await context.route("**/example.json", async (route) => {
- console.log("[route] Intercepting:", route.request().url());
-
- // Mock the response entirely:
- await route.fulfill({
- status: 200,
- contentType: "application/json",
- body: JSON.stringify({ mockedData: 1234 }),
- });
- });
-
- // 2. routeWebSocket: intercept "/socket"
- await context.routeWebSocket("**/socket", async (pageSideRoute) => {
- console.log("Intercepting WebSocket at:", pageSideRoute.url());
-
- // Connect to the real server
- const serverSideRoute = pageSideRoute.connectToServer();
-
- // Page -> Server
- pageSideRoute.onMessage((msg) => {
- console.log("Page -> Server message:", msg);
- // Forward to server side
- serverSideRoute.send(msg);
- });
-
- // Server -> Page
- serverSideRoute.onMessage((msg) => {
- console.log("Server -> Page message:", msg);
- pageSideRoute.send(msg);
- });
- });
-
- // 3. Open a page and fetch /example.json
- const page = await context.newPage();
- await page.goto(baseURL);
-
- const fetchResult = await page.evaluate(async () => {
- const res = await fetch("/example.json");
- return res.json();
- });
- // We should get the mocked data from our route, not the real 'server-data'
- expect(fetchResult.mockedData).toBe(1234);
-
- // 4. Test the WebSocket
- // We'll store messages from the server in an array so we can assert them
- const wsMessages: string[] = [];
- page.on("console", (msg) => {
- // We'll parse out the console logs we used for WebSocket
- if (msg.type() === "log") {
- wsMessages.push(msg.text());
- }
- });
-
- // Create a WS from the page
- await page.evaluate((port) => {
- const ws = new WebSocket(`ws://localhost:${port}/socket`);
- ws.onmessage = (evt) => {
- console.log(`WS message from server: ${evt.data}`);
- };
- setTimeout(() => {
- // send a message from the page side
- ws.send("Hello from the client");
- }, 1000);
- }, serverPort);
-
- // Wait a moment for messages
- await page.waitForTimeout(3000);
-
- // We expect the server to have initially sent "Hello from server WebSocket"
- // And also an echo of "Hello from the client" => "Server echo: Hello from the client"
- const initialHello = wsMessages.find((m) =>
- m.includes("Hello from server WebSocket"),
- );
- expect(initialHello).toBeTruthy();
-
- const echoMessage = wsMessages.find((m) =>
- m.includes("Server echo: Hello from the client"),
- );
- expect(echoMessage).toBeTruthy();
-
- // 5. unroute the JSON route
- await context.unroute("**/example.json");
-
- // 6. confirm the WebSocket route is still active
- // do a second fetch -> This time it won't be mocked
- const fetchResult2 = await page.evaluate(async () => {
- const res = await fetch("/example.json");
- return res.json();
- });
- // The real server returns { original: "server-data" }
- expect(fetchResult2.original).toBe("server-data");
-
- // 7. unrouteAll
- await context.unrouteAll();
- });
-
- test("should demonstrate routeFromHar usage", async () => {
- const harPath = path.join(__dirname, "tmp-test.har");
-
- const dynamicHar = HAR_CONTENT.replace(
- "http://localhost/har-example.json",
- `http://localhost:${serverPort}/har-example.json`,
- );
-
- fs.writeFileSync(harPath, dynamicHar, "utf-8");
-
- const context = stagehand.context;
-
- await context.routeFromHAR(harPath, { update: false });
-
- const page = await context.newPage();
- await page.goto(`http://localhost:${serverPort}/har-example.json`);
-
- const bodyText = await page.evaluate(() => document.body.innerText);
- console.log("HAR-based body text:", bodyText);
- expect(bodyText).toContain("harKey");
- expect(bodyText).toContain("harValue");
-
- await context.unrouteAll();
- fs.unlinkSync(harPath);
- });
-});
diff --git a/evals/deterministic/tests/Errors/apiKeyError.test.ts b/evals/deterministic/tests/Errors/apiKeyError.test.ts
deleted file mode 100644
index 2cdc6656f..000000000
--- a/evals/deterministic/tests/Errors/apiKeyError.test.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-import { z } from "zod";
-
-test.describe("API key/LLMClient error", () => {
- test("Should confirm that we get an error if we call extract without LLM API key or LLMClient", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- await stagehand.page.goto("https://docs.browserbase.com/introduction");
-
- let errorThrown: Error | null = null;
-
- try {
- await stagehand.page.extract({
- instruction:
- "From the introduction page, extract the explanation of what Browserbase is.",
- schema: z.object({
- stars: z.string().describe("the explanation of what Browserbase is"),
- }),
- });
- } catch (error) {
- errorThrown = error as Error;
- }
-
- expect(errorThrown).toBeInstanceOf(Error);
- expect(errorThrown?.message).toContain(
- "No LLM API key or LLM Client configured",
- );
-
- await stagehand.close();
- });
-
- test("Should confirm that we get an error if we call act without LLM API key or LLMClient", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- await stagehand.page.goto("https://docs.browserbase.com/introduction");
-
- let errorThrown: Error | null = null;
-
- try {
- await stagehand.page.act({
- action: "Click on the 'Quickstart' section",
- });
- } catch (error) {
- errorThrown = error as Error;
- }
-
- expect(errorThrown).toBeInstanceOf(Error);
- expect(errorThrown?.message).toContain(
- "No LLM API key or LLM Client configured",
- );
-
- await stagehand.close();
- });
-
- test("Should confirm that we get an error if we call observe without LLM API key or LLMClient", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- await stagehand.page.goto("https://docs.browserbase.com/introduction");
-
- let errorThrown: Error | null = null;
-
- try {
- await stagehand.page.observe();
- } catch (error) {
- errorThrown = error as Error;
- }
-
- expect(errorThrown).toBeInstanceOf(Error);
- expect(errorThrown?.message).toContain(
- "No LLM API key or LLM Client configured",
- );
-
- await stagehand.close();
- });
-});
diff --git a/evals/deterministic/tests/browserbase/contexts.test.ts b/evals/deterministic/tests/browserbase/contexts.test.ts
deleted file mode 100644
index 0f8ecea9c..000000000
--- a/evals/deterministic/tests/browserbase/contexts.test.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-import Browserbase from "@browserbasehq/sdk";
-import { expect, test } from "@playwright/test";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-import { Stagehand } from "@/dist";
-
-// Configuration
-const CONTEXT_TEST_URL = "https://docs.browserbase.com";
-const BROWSERBASE_PROJECT_ID = process.env.BROWSERBASE_PROJECT_ID!;
-const BROWSERBASE_API_KEY = process.env.BROWSERBASE_API_KEY!;
-
-const bb = new Browserbase({
- apiKey: BROWSERBASE_API_KEY,
-});
-
-// Helper functions
-function addHour(date: Date): number {
- const SECOND = 1000;
- return new Date(date.getTime() + 60 * 60 * 1000).getTime() / SECOND;
-}
-
-async function findCookie(stagehand: Stagehand, name: string) {
- const defaultContext = stagehand.context;
- const cookies = await defaultContext?.cookies();
- return cookies?.find((cookie) => cookie.name === name);
-}
-
-async function createContext() {
- console.log("Creating a new context...");
- const context = await bb.contexts.create({
- projectId: BROWSERBASE_PROJECT_ID,
- });
- const contextId = context.id;
- console.log(`Context created with ID: ${contextId}`);
- return contextId;
-}
-
-async function setRandomCookie(contextId: string, stagehand: Stagehand) {
- console.log(
- `Populating context ${contextId} during session ${stagehand.browserbaseSessionID}`,
- );
- const page = stagehand.page;
-
- await page.goto(CONTEXT_TEST_URL, { waitUntil: "domcontentloaded" });
-
- const now = new Date();
- const testCookieName = `bb_${now.getTime().toString()}`;
- const testCookieValue = now.toISOString();
-
- await stagehand.context.addCookies([
- {
- domain: `.${new URL(CONTEXT_TEST_URL).hostname}`,
- expires: addHour(now),
- name: testCookieName,
- path: "/",
- value: testCookieValue,
- },
- ]);
-
- expect(findCookie(stagehand, testCookieName)).toBeDefined();
- console.log(`Set test cookie: ${testCookieName}=${testCookieValue}`);
- return { testCookieName, testCookieValue };
-}
-
-test.describe("Contexts", () => {
- test("Persists and re-uses a context", async () => {
- let contextId: string;
- let testCookieName: string;
- let testCookieValue: string;
- let stagehand: Stagehand;
-
- await test.step("Create a context", async () => {
- contextId = await createContext();
- });
-
- await test.step("Instantiate Stagehand with the context to persist", async () => {
- // We will be adding cookies to the context in this session, so we need mark persist=true
- stagehand = new Stagehand({
- ...StagehandConfig,
- browserbaseSessionCreateParams: {
- projectId: BROWSERBASE_PROJECT_ID,
- browserSettings: {
- context: {
- id: contextId,
- persist: true,
- },
- },
- },
- });
- await stagehand.init();
- });
-
- await test.step("Set a random cookie on the page", async () => {
- ({ testCookieName } = await setRandomCookie(contextId, stagehand));
-
- const page = stagehand.page;
- await page.goto("https://www.google.com", {
- waitUntil: "domcontentloaded",
- });
- await page.goBack();
- });
-
- await test.step("Validate cookie persistence between pages", async () => {
- const cookie = await findCookie(stagehand, testCookieName);
- const found = !!cookie;
- expect(found).toBe(true);
- console.log("Cookie persisted between pages:", found);
-
- await stagehand.close();
- // Wait for context to persist
- console.log("Waiting for context to persist...");
- await new Promise((resolve) => setTimeout(resolve, 5000));
- });
-
- await test.step("Create another session with the same context", async () => {
- // We don't need to persist cookies in this session, so we can mark persist=false
- const newStagehand = new Stagehand({
- ...StagehandConfig,
- browserbaseSessionCreateParams: {
- projectId: BROWSERBASE_PROJECT_ID,
- browserSettings: {
- context: {
- id: contextId,
- persist: false,
- },
- },
- },
- });
- await newStagehand.init();
- console.log(
- `Reusing context ${contextId} during session ${newStagehand.browserbaseSessionID}`,
- );
- const newPage = newStagehand.page;
- await newPage.goto(CONTEXT_TEST_URL, { waitUntil: "domcontentloaded" });
-
- const foundCookie = await findCookie(newStagehand, testCookieName);
- console.log("Cookie found in new session:", !!foundCookie);
- console.log(
- "Cookie value matches:",
- foundCookie?.value === testCookieValue,
- );
-
- await newStagehand.close();
- });
- });
-});
diff --git a/evals/deterministic/tests/browserbase/downloads.test.ts b/evals/deterministic/tests/browserbase/downloads.test.ts
deleted file mode 100644
index 10366b6cd..000000000
--- a/evals/deterministic/tests/browserbase/downloads.test.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { test, expect } from "@playwright/test";
-import AdmZip from "adm-zip";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-import { Stagehand } from "@/dist";
-import Browserbase from "@browserbasehq/sdk";
-
-const downloadRe = /sandstorm-(\d{13})+\.mp3/;
-
-test("Downloads", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- const page = stagehand.page;
- const context = stagehand.context;
-
- const client = await context.newCDPSession(page);
- await client.send("Browser.setDownloadBehavior", {
- behavior: "allow",
- // `downloadPath` gets appended to the browser's default download directory.
- // set to "downloads", it ends up being "/app/apps/browser/downloads/".
- downloadPath: "downloads",
- eventsEnabled: true,
- });
-
- await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");
-
- const [download] = await Promise.all([
- page.waitForEvent("download"),
- page.locator("#download").click(),
- ]);
-
- const downloadError = await download.failure();
-
- await stagehand.close();
-
- if (downloadError !== null) {
- throw new Error(
- `Download for session ${stagehand.browserbaseSessionID} failed: ${downloadError}`,
- );
- }
-
- expect(async () => {
- const bb = new Browserbase();
- const zipBuffer = await bb.sessions.downloads.list(
- stagehand.browserbaseSessionID,
- );
- if (!zipBuffer) {
- throw new Error(
- `Download buffer is empty for session ${stagehand.browserbaseSessionID}`,
- );
- }
-
- const zip = new AdmZip(Buffer.from(await zipBuffer.arrayBuffer()));
- const zipEntries = zip.getEntries();
- const mp3Entry = zipEntries.find((entry) =>
- downloadRe.test(entry.entryName),
- );
-
- if (!mp3Entry) {
- throw new Error(
- `Session ${stagehand.browserbaseSessionID} is missing a file matching "${downloadRe.toString()}" in its zip entries: ${JSON.stringify(zipEntries.map((entry) => entry.entryName))}`,
- );
- }
-
- const expectedFileSize = 6137541;
- expect(mp3Entry.header.size).toBe(expectedFileSize);
- }).toPass({
- timeout: 30_000,
- });
-});
diff --git a/evals/deterministic/tests/browserbase/sessions.test.ts b/evals/deterministic/tests/browserbase/sessions.test.ts
deleted file mode 100644
index 67c97659c..000000000
--- a/evals/deterministic/tests/browserbase/sessions.test.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-import Browserbase from "@browserbasehq/sdk";
-
-test.describe("Browserbase Sessions", () => {
- let browserbase: Browserbase;
- let sessionId: string;
- let bigStagehand: Stagehand;
-
- test.beforeAll(async () => {
- browserbase = new Browserbase({
- apiKey: process.env.BROWSERBASE_API_KEY,
- });
- bigStagehand = new Stagehand({
- ...StagehandConfig,
- env: "BROWSERBASE",
- browserbaseSessionCreateParams: {
- projectId: process.env.BROWSERBASE_PROJECT_ID,
- keepAlive: true,
- },
- });
- await bigStagehand.init();
- await bigStagehand.page.goto(
- "https://docs.stagehand.dev/get_started/introduction",
- );
- sessionId = bigStagehand.browserbaseSessionID;
- if (!sessionId) {
- throw new Error("Failed to get browserbase session ID");
- }
- });
- test.afterAll(async () => {
- await bigStagehand.close();
- });
- test("resumes a session via sessionId", async () => {
- const stagehand = new Stagehand({
- ...StagehandConfig,
- env: "BROWSERBASE",
- browserbaseSessionID: sessionId,
- });
- await stagehand.init();
-
- const page = stagehand.page;
-
- expect(page.url()).toBe(
- "https://docs.stagehand.dev/get_started/introduction",
- );
- await stagehand.close();
- });
- test("resumes a session via CDP URL", async () => {
- const session = await browserbase.sessions.retrieve(sessionId);
- const stagehand = new Stagehand({
- ...StagehandConfig,
- env: "LOCAL",
- localBrowserLaunchOptions: {
- headless: true,
- cdpUrl: session.connectUrl,
- },
- });
- await stagehand.init();
- const page = stagehand.page;
-
- expect(page.url()).toBe(
- "https://docs.stagehand.dev/get_started/introduction",
- );
- });
-});
diff --git a/evals/deterministic/tests/browserbase/uploads.test.ts b/evals/deterministic/tests/browserbase/uploads.test.ts
deleted file mode 100644
index 06b724423..000000000
--- a/evals/deterministic/tests/browserbase/uploads.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { join } from "node:path";
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("Playwright Upload", () => {
- let stagehand: Stagehand;
-
- test.beforeAll(async () => {
- stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- });
-
- test.afterAll(async () => {
- await stagehand.close();
- });
-
- test("uploads a file", async () => {
- const page = stagehand.page;
- await page.goto("https://browser-tests-alpha.vercel.app/api/upload-test");
-
- const fileInput = page.locator("#fileUpload");
- await fileInput.setInputFiles(
- join(__dirname, "../..", "auxiliary", "logo.png"),
- );
-
- const fileNameSpan = page.locator("#fileName");
- const fileName = await fileNameSpan.innerText();
-
- const fileSizeSpan = page.locator("#fileSize");
- const fileSize = Number(await fileSizeSpan.innerText());
-
- expect(fileName).toBe("logo.png");
- expect(fileSize).toBeGreaterThan(0);
- });
-});
diff --git a/evals/deterministic/tests/local/create.test.ts b/evals/deterministic/tests/local/create.test.ts
deleted file mode 100644
index 48f78f4cb..000000000
--- a/evals/deterministic/tests/local/create.test.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import path from "path";
-import fs from "fs";
-import os from "os";
-import type { Cookie } from "@playwright/test";
-import StagehandConfig from "../../stagehand.config";
-
-test.describe("Local browser launch options", () => {
- test("launches with default options when no localBrowserLaunchOptions provided", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const context = stagehand.context;
- expect(context.browser()).toBeDefined();
- expect(context.pages().length).toBe(1);
-
- await stagehand.close();
- });
-
- test("respects custom userDataDir", async () => {
- const customUserDataDir = path.join(os.tmpdir(), "custom-user-data");
-
- const stagehand = new Stagehand({
- ...StagehandConfig,
- localBrowserLaunchOptions: {
- userDataDir: customUserDataDir,
- headless: true,
- },
- });
- await stagehand.init();
-
- expect(fs.existsSync(customUserDataDir)).toBeTruthy();
-
- await stagehand.close();
-
- // Cleanup
- fs.rmSync(customUserDataDir, { recursive: true, force: true });
- });
-
- test("applies custom viewport settings", async () => {
- const customViewport = { width: 1920, height: 1080 };
-
- const stagehand = new Stagehand({
- ...StagehandConfig,
- localBrowserLaunchOptions: {
- ...StagehandConfig.localBrowserLaunchOptions,
- viewport: customViewport,
- },
- });
- await stagehand.init();
-
- const page = await stagehand.context.newPage();
- const viewport = page.viewportSize();
-
- expect(viewport).toEqual(customViewport);
-
- await stagehand.close();
- });
-
- test("applies custom cookies", async () => {
- const testCookies: Cookie[] = [
- {
- name: "testCookie",
- value: "testValue",
- domain: "example.com",
- path: "/",
- expires: -1,
- httpOnly: false,
- secure: false,
- sameSite: "Lax" as const,
- },
- ];
-
- const stagehand = new Stagehand({
- ...StagehandConfig,
- localBrowserLaunchOptions: {
- ...StagehandConfig.localBrowserLaunchOptions,
- cookies: testCookies,
- },
- });
- await stagehand.init();
-
- const page = await stagehand.context.newPage();
- await page.goto("https://example.com");
- const cookies = await stagehand.context.cookies();
-
- expect(cookies[0]).toMatchObject(
- testCookies[0] as unknown as Record,
- );
-
- await stagehand.close();
- });
-
- test("applies custom geolocation settings", async () => {
- const customGeolocation = {
- latitude: 40.7128,
- longitude: -74.006,
- };
-
- const stagehand = new Stagehand({
- ...StagehandConfig,
- localBrowserLaunchOptions: {
- ...StagehandConfig.localBrowserLaunchOptions,
- geolocation: customGeolocation,
- permissions: ["geolocation"],
- },
- });
- await stagehand.init();
-
- const page = await stagehand.context.newPage();
- await page.goto("https://example.com");
-
- const location = await page.evaluate(() => {
- return new Promise((resolve) => {
- navigator.geolocation.getCurrentPosition(
- (position) => {
- resolve({
- latitude: position.coords.latitude,
- longitude: position.coords.longitude,
- });
- },
- () => resolve(null),
- );
- });
- });
-
- expect(location).toEqual(customGeolocation);
-
- await stagehand.close();
- });
-
- test("applies custom timezone and locale", async () => {
- const stagehand = new Stagehand({
- ...StagehandConfig,
- localBrowserLaunchOptions: {
- ...StagehandConfig.localBrowserLaunchOptions,
- locale: "ja-JP",
- timezoneId: "Asia/Tokyo",
- },
- });
- await stagehand.init();
-
- const page = await stagehand.context.newPage();
- await page.goto("https://example.com");
-
- const { locale, timezone } = await page.evaluate(() => ({
- locale: navigator.language,
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
- }));
-
- expect(locale).toBe("ja-JP");
- expect(timezone).toBe("Asia/Tokyo");
-
- await stagehand.close();
- });
-
- test("records video when enabled", async () => {
- const videoDir = path.join(os.tmpdir(), "test-videos");
- fs.mkdirSync(videoDir, { recursive: true });
-
- const stagehand = new Stagehand({
- ...StagehandConfig,
- localBrowserLaunchOptions: {
- ...StagehandConfig.localBrowserLaunchOptions,
- recordVideo: {
- dir: videoDir,
- size: { width: 800, height: 600 },
- },
- },
- });
- await stagehand.init();
-
- const page = await stagehand.context.newPage();
- await page.goto("https://example.com");
- await stagehand.close();
-
- const videos = fs.readdirSync(videoDir);
- expect(videos.length).toBeGreaterThan(0);
- expect(videos[0]).toMatch(/\.webm$/);
-
- // Cleanup
- fs.rmSync(videoDir, { recursive: true, force: true });
- });
-});
diff --git a/evals/deterministic/tests/page/addInitScript.test.ts b/evals/deterministic/tests/page/addInitScript.test.ts
deleted file mode 100644
index 56be8ef41..000000000
--- a/evals/deterministic/tests/page/addInitScript.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - addInitScript", () => {
- test("should inject a script before the page loads", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
-
- await page.addInitScript(() => {
- const w = window as typeof window & {
- __testInitScriptVar?: string;
- };
- w.__testInitScriptVar = "Hello from init script!";
- });
-
- await page.goto("https://example.com");
-
- const result = await page.evaluate(() => {
- const w = window as typeof window & {
- __testInitScriptVar?: string;
- };
- return w.__testInitScriptVar;
- });
- expect(result).toBe("Hello from init script!");
-
- await page.goto("https://docs.browserbase.com/");
- const resultAfterNavigation = await page.evaluate(() => {
- const w = window as typeof window & {
- __testInitScriptVar?: string;
- };
- return w.__testInitScriptVar;
- });
- expect(resultAfterNavigation).toBe("Hello from init script!");
-
- await stagehand.close();
- });
-});
diff --git a/evals/deterministic/tests/page/addRemoveLocatorHandler.test.ts b/evals/deterministic/tests/page/addRemoveLocatorHandler.test.ts
deleted file mode 100644
index a46abc92d..000000000
--- a/evals/deterministic/tests/page/addRemoveLocatorHandler.test.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - addLocatorHandler and removeLocatorHandler", () => {
- // This HTML snippet is reused by both tests.
- // The "Sign up to the newsletter" overlay appears after 2 seconds.
- // The "No thanks" button hides it.
- const overlayHTML = `
-
-
- Start here
-
-
Sign up to the newsletter
-
No thanks
-
-
-
-
- `;
-
- test("should use a custom locator handler to dismiss the overlay", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const { page } = stagehand;
-
- await page.addLocatorHandler(
- page.getByText("Sign up to the newsletter"),
- async () => {
- console.log("Overlay detected. Clicking 'No thanks' to remove it...");
- await page.getByRole("button", { name: "No thanks" }).click();
- },
- );
-
- await page.goto("https://example.com");
- await page.setContent(overlayHTML);
-
- await page.waitForTimeout(5000);
-
- await page.getByRole("button", { name: "Start here" }).click();
-
- const isOverlayVisible = await page
- .getByText("Sign up to the newsletter")
- .isVisible()
- .catch(() => false);
-
- await stagehand.close();
-
- expect(isOverlayVisible).toBeFalsy();
- });
-
- test("should remove a custom locator handler so overlay stays visible", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const { page } = stagehand;
-
- const locator = page.getByText("Sign up to the newsletter");
- await page.addLocatorHandler(locator, async () => {
- console.log("Overlay detected. Clicking 'No thanks' to remove it...");
- await page.getByRole("button", { name: "No thanks" }).click();
- });
-
- await page.removeLocatorHandler(locator);
- console.log("Locator handler removed — overlay will not be dismissed now.");
-
- await page.goto("https://example.com");
- await page.setContent(overlayHTML);
-
- await page.waitForTimeout(5000);
-
- await page.getByRole("button", { name: "Start here" }).click();
-
- const isOverlayVisible = await page
- .getByText("Sign up to the newsletter")
- .isVisible()
- .catch(() => false);
-
- await stagehand.close();
- expect(isOverlayVisible).toBe(true);
- });
-});
diff --git a/evals/deterministic/tests/page/addTags.test.ts b/evals/deterministic/tests/page/addTags.test.ts
deleted file mode 100644
index 463d109a2..000000000
--- a/evals/deterministic/tests/page/addTags.test.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - addScriptTag and addStyleTag", () => {
- let stagehand: Stagehand;
-
- test.beforeAll(async () => {
- stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- });
-
- test.afterAll(async () => {
- await stagehand.close();
- });
-
- test("should inject a script tag and have access to the defined function", async () => {
- const { page } = stagehand;
-
- await page.setContent(`
-
-
- Hello, world!
-
-
- `);
-
- await page.addScriptTag({
- content: `
- window.sayHello = function() {
- document.getElementById("greeting").textContent = "Hello from injected script!";
- }
- `,
- });
-
- await page.evaluate(() => {
- const w = window as typeof window & {
- sayHello?: () => void;
- };
- w.sayHello?.();
- });
-
- const text = await page.locator("#greeting").textContent();
- expect(text).toBe("Hello from injected script!");
- });
-
- test("should inject a style tag and apply styles", async () => {
- const { page } = stagehand;
-
- await page.setContent(`
-
-
- Some text
-
-
- `);
-
- await page.addStyleTag({
- content: `
- #styledDiv {
- color: red;
- font-weight: bold;
- }
- `,
- });
-
- const color = await page.evaluate(() => {
- const el = document.getElementById("styledDiv");
- return window.getComputedStyle(el!).color;
- });
- expect(color).toBe("rgb(255, 0, 0)");
-
- const fontWeight = await page.evaluate(() => {
- const el = document.getElementById("styledDiv");
- return window.getComputedStyle(el!).fontWeight;
- });
- expect(["bold", "700"]).toContain(fontWeight);
- });
-});
diff --git a/evals/deterministic/tests/page/bringToFront.test.ts b/evals/deterministic/tests/page/bringToFront.test.ts
deleted file mode 100644
index d25ddb6c3..000000000
--- a/evals/deterministic/tests/page/bringToFront.test.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - bringToFront", () => {
- test("should bring a background page to the front and allow further actions", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const { page: page1 } = stagehand;
-
- const page2 = await stagehand.context.newPage();
- await page2.goto("https://example.com");
- const page2Title = await page2.title();
- console.log("Page2 Title:", page2Title);
-
- await page1.goto("https://www.google.com");
- const page1TitleBefore = await page1.title();
- console.log("Page1 Title before:", page1TitleBefore);
-
- await page1.bringToFront();
-
- await page1.goto("https://docs.browserbase.com");
- const page1TitleAfter = await page1.title();
- console.log("Page1 Title after:", page1TitleAfter);
-
- await page2.bringToFront();
- const page2URLBefore = page2.url();
- console.log("Page2 URL before navigation:", page2URLBefore);
-
- await stagehand.close();
-
- expect(page1TitleBefore).toContain("Google");
- expect(page1TitleAfter).toContain("Browserbase");
- expect(page2Title).toContain("Example Domain");
- });
-});
diff --git a/evals/deterministic/tests/page/content.test.ts b/evals/deterministic/tests/page/content.test.ts
deleted file mode 100644
index fe95aae69..000000000
--- a/evals/deterministic/tests/page/content.test.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - content", () => {
- test("should retrieve the full HTML content of the page", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://example.com");
- const html = await page.content();
- expect(html).toContain("Example Domain ");
- expect(html).toContain("Example Domain ");
-
- await stagehand.close();
- });
-});
diff --git a/evals/deterministic/tests/page/evaluate.test.ts b/evals/deterministic/tests/page/evaluate.test.ts
deleted file mode 100644
index 2610735be..000000000
--- a/evals/deterministic/tests/page/evaluate.test.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - JavaScript Evaluation", () => {
- test("can evaluate JavaScript in the page context", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
-
- await page.goto("https://example.com");
-
- const sum = await page.evaluate(() => 2 + 2);
- expect(sum).toBe(4);
-
- const pageTitle = await page.evaluate(() => document.title);
- expect(pageTitle).toMatch(/example/i);
-
- const obj = await page.evaluate(() => {
- return {
- message: "Hello from the browser",
- userAgent: navigator.userAgent,
- };
- });
- expect(obj).toHaveProperty("message", "Hello from the browser");
- expect(obj.userAgent).toBeDefined();
-
- await stagehand.close();
- });
-});
diff --git a/evals/deterministic/tests/page/expose.test.ts b/evals/deterministic/tests/page/expose.test.ts
deleted file mode 100644
index 9572b39c0..000000000
--- a/evals/deterministic/tests/page/expose.test.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - evaluateHandle, exposeBinding, exposeFunction", () => {
- let stagehand: Stagehand;
-
- test.beforeAll(async () => {
- stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- });
-
- test.afterAll(async () => {
- await stagehand.close();
- });
-
- test("demonstrates evaluateHandle, exposeBinding, and exposeFunction", async () => {
- const { page } = stagehand;
-
- await page.setContent(`
-
-
- Initial Text
-
-
- `);
-
- const divHandle = await page.evaluateHandle(() => {
- return document.getElementById("myDiv");
- });
- await divHandle.evaluate((div, newText) => {
- div.textContent = newText;
- }, "Text updated via evaluateHandle");
-
- const text = await page.locator("#myDiv").textContent();
- expect(text).toBe("Text updated via evaluateHandle");
-
- await page.exposeBinding("myBinding", async (source, arg: string) => {
- console.log("myBinding called from page with arg:", arg);
- return `Node responded with: I got your message: "${arg}"`;
- });
-
- const responseFromBinding = await page.evaluate(async () => {
- const w = window as typeof window & {
- myBinding?: (arg: string) => Promise;
- };
- return w.myBinding?.("Hello from the browser");
- });
- expect(responseFromBinding).toMatch(/I got your message/);
-
- await page.exposeFunction("addNumbers", (a: number, b: number) => {
- return a + b;
- });
-
- const sum = await page.evaluate(async () => {
- const w = window as typeof window & {
- addNumbers?: (a: number, b: number) => number;
- };
- return w.addNumbers?.(3, 7);
- });
- expect(sum).toBe(10);
- });
-});
diff --git a/evals/deterministic/tests/page/frames.test.ts b/evals/deterministic/tests/page/frames.test.ts
deleted file mode 100644
index 18c50942c..000000000
--- a/evals/deterministic/tests/page/frames.test.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - frame operations", () => {
- let stagehand: Stagehand;
-
- test.beforeAll(async () => {
- stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- });
-
- test.afterAll(async () => {
- await stagehand.close();
- });
-
- test("should use page.mainFrame(), page.frames(), page.frame(), and page.frameLocator()", async () => {
- const { page } = stagehand;
-
- await page.setContent(`
-
-
-
-
-
-
-
- `);
-
- await page.waitForSelector('iframe[name="frame-one"]');
- await page.waitForSelector('iframe[name="frame-two"]');
-
- const frames = page.frames();
- console.log(
- "All frames found:",
- frames.map((f) => f.name()),
- );
- expect(frames).toHaveLength(3);
-
- const mainFrame = page.mainFrame();
- console.log("Main frame name:", mainFrame.name());
- expect(mainFrame.name()).toBe("");
-
- const frameOne = page.frame({ name: "frame-one" });
- expect(frameOne).not.toBeNull();
-
- const frameOneText = await frameOne?.locator("h1").textContent();
- expect(frameOneText).toBe("Hello from Frame 1");
-
- const frameTwoLocator = page.frameLocator("iframe[name='frame-two']");
- const frameTwoText = await frameTwoLocator.locator("h1").textContent();
- expect(frameTwoText).toBe("Hello from Frame 2");
-
- const frameTwo = page.frame({ name: "frame-two" });
- expect(frameTwo).not.toBeNull();
-
- const frameTwoTextAgain = await frameTwo?.locator("h1").textContent();
- expect(frameTwoTextAgain).toBe("Hello from Frame 2");
- });
-});
diff --git a/evals/deterministic/tests/page/getBy.test.ts b/evals/deterministic/tests/page/getBy.test.ts
deleted file mode 100644
index 5da3a3976..000000000
--- a/evals/deterministic/tests/page/getBy.test.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - Built-in locators", () => {
- let stagehand: Stagehand;
-
- test.beforeAll(async () => {
- stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- });
-
- test.afterAll(async () => {
- await stagehand.close();
- });
-
- test("demonstrates getByAltText, getByLabel, getByPlaceholder, getByRole, getByTestId, getByText, getByTitle", async () => {
- const { page } = stagehand;
- await page.setContent(`
-
-
-
- Username
-
-
- Sign in
- Hello World!
- This is some descriptive text on the page.
- Site Title
-
-
- `);
- const image = page.getByAltText("Profile picture");
- await expect(image).toBeVisible();
- const usernameInput = page.getByLabel("Username");
- await expect(usernameInput).toBeVisible();
- const emailInput = page.getByPlaceholder("Enter your email");
- await expect(emailInput).toBeVisible();
- const signInButton = page.getByRole("button", { name: "Sign in" });
- await expect(signInButton).toBeVisible();
- const greetingDiv = page.getByTestId("greeting");
- await expect(greetingDiv).toHaveText("Hello World!");
- const descriptiveText = page.getByText(
- "This is some descriptive text on the page.",
- );
- await expect(descriptiveText).toBeVisible();
- const heading = page.getByTitle("A heading for the page");
- await expect(heading).toHaveText("Site Title");
- });
-});
diff --git a/evals/deterministic/tests/page/navigation.test.ts b/evals/deterministic/tests/page/navigation.test.ts
deleted file mode 100644
index e9acb7a7c..000000000
--- a/evals/deterministic/tests/page/navigation.test.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - Navigation", () => {
- test("should navigate back and forward between pages", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
-
- await page.goto("https://example.com");
- expect(page.url()).toBe("https://example.com/");
-
- await page.goto("https://docs.browserbase.com/introduction");
- expect(page.url()).toBe("https://docs.browserbase.com/introduction");
-
- await page.goBack();
- expect(page.url()).toBe("https://example.com/");
-
- await page.goForward();
- expect(page.url()).toBe("https://docs.browserbase.com/introduction");
-
- await stagehand.close();
- });
-});
diff --git a/evals/deterministic/tests/page/on.test.ts b/evals/deterministic/tests/page/on.test.ts
deleted file mode 100644
index 97ec5bbfe..000000000
--- a/evals/deterministic/tests/page/on.test.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import { expect, test } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - page.on()", () => {
- test("should click on the crewAI blog tab", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto(
- "https://docs.browserbase.com/integrations/crew-ai/introduction",
- );
-
- let clickPromise: Promise;
-
- page.on("popup", async (newPage) => {
- clickPromise = newPage.click(
- "body > div.page-wrapper > div.navbar-2.w-nav > div.padding-global.top-bot > div > div.navigation-left > nav > a:nth-child(7)",
- );
- });
-
- await page.goto(
- "https://docs.browserbase.com/integrations/crew-ai/introduction",
- );
-
- await page.click(
- "#content-area > div.relative.mt-8.prose.prose-gray.dark\\:prose-invert > p:nth-child(2) > a",
- );
-
- await clickPromise;
-
- await stagehand.close();
- });
-
- test("should close the new tab and navigate to it on the existing page", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto(
- "https://docs.browserbase.com/integrations/crew-ai/introduction",
- );
-
- let navigatePromise: Promise;
-
- page.on("popup", async (newPage) => {
- navigatePromise = Promise.allSettled([
- newPage.close(),
- page.goto(newPage.url(), { waitUntil: "domcontentloaded" }),
- ]);
- });
-
- // Click on the crewAI blog tab
- await page.click(
- "#content-area > div.relative.mt-8.prose.prose-gray.dark\\:prose-invert > p:nth-child(2) > a",
- );
-
- await navigatePromise;
-
- await page.click(
- "body > div.page-wrapper > div.navbar-2.w-nav > div.padding-global.top-bot > div > div.navigation-left > nav > a:nth-child(3)",
- );
-
- await page.waitForLoadState("domcontentloaded");
-
- const currentUrl = page.url();
- expect(currentUrl).toBe("https://www.crewai.com/open-source");
-
- await stagehand.close();
- });
-
- test("should handle console events", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://example.com");
-
- const messages: string[] = [];
- page.on("console", (msg) => {
- messages.push(msg.text());
- });
-
- await page.evaluate(() => console.log("Test console log"));
-
- expect(messages).toContain("Test console log");
-
- await stagehand.close();
- });
-
- test("should handle dialog events", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://example.com");
-
- page.on("dialog", async (dialog) => {
- expect(dialog.message()).toBe("Test alert");
- await dialog.dismiss();
- });
-
- await page.evaluate(() => alert("Test alert"));
-
- await stagehand.close();
- });
-
- test("should handle request and response events", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://example.com");
-
- const requests: string[] = [];
- const responses: string[] = [];
-
- page.on("request", (request) => {
- requests.push(request.url());
- });
-
- page.on("response", (response) => {
- responses.push(response.url());
- });
-
- await page.goto("https://example.com");
-
- expect(requests).toContain("https://example.com/");
- expect(responses).toContain("https://example.com/");
-
- await stagehand.close();
- });
-});
diff --git a/evals/deterministic/tests/page/pageContext.test.ts b/evals/deterministic/tests/page/pageContext.test.ts
deleted file mode 100644
index a0f36911c..000000000
--- a/evals/deterministic/tests/page/pageContext.test.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - page.context()", () => {
- let stagehand: Stagehand;
-
- test.beforeEach(async () => {
- stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
- });
-
- test.afterEach(async () => {
- if (stagehand) {
- try {
- await stagehand.close();
- } catch (error) {
- console.error("[afterEach] Error during stagehand.close():", error);
- }
- } else {
- console.log("[afterEach] Stagehand was not defined, skipping close().");
- }
- });
-
- test("should confirm page.context() and stagehand.context share state", async () => {
- const page = stagehand.page;
- const stagehandContext = stagehand.context;
- const pageContext = page.context();
-
- await pageContext.addCookies([
- {
- name: "stagehandTestCookie",
- value: "hello-stagehand",
- domain: "example.com",
- path: "/",
- expires: Math.floor(Date.now() / 1000) + 3600, // 1 hour
- httpOnly: false,
- secure: false,
- sameSite: "Lax",
- },
- ]);
-
- const cookies = await stagehandContext.cookies("https://example.com");
-
- const testCookie = cookies.find((c) => c.name === "stagehandTestCookie");
- expect(testCookie).toBeDefined();
- expect(testCookie?.value).toBe("hello-stagehand");
-
- const extraPage = await pageContext.newPage();
- await extraPage.goto("https://example.com");
- const contextPages = stagehandContext.pages();
-
- // The newly created page should be recognized by stagehandContext as well.
- const foundExtraPage = contextPages.find(
- (p) => p.url() === "https://example.com/",
- );
- expect(foundExtraPage).toBeDefined();
- });
-});
diff --git a/evals/deterministic/tests/page/reload.test.ts b/evals/deterministic/tests/page/reload.test.ts
deleted file mode 100644
index 31518a747..000000000
--- a/evals/deterministic/tests/page/reload.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - Reload", () => {
- test("should reload the page and reset page state", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://docs.browserbase.com/");
-
- await page.evaluate(() => {
- const w = window as typeof window & {
- __testReloadMarker?: string;
- };
- w.__testReloadMarker = "Hello Reload!";
- });
-
- const markerBeforeReload = await page.evaluate(() => {
- const w = window as typeof window & {
- __testReloadMarker?: string;
- };
- return w.__testReloadMarker;
- });
- expect(markerBeforeReload).toBe("Hello Reload!");
-
- await page.reload();
-
- const markerAfterReload = await page.evaluate(() => {
- const w = window as typeof window & {
- __testReloadMarker?: string;
- };
- return w.__testReloadMarker;
- });
- expect(markerAfterReload).toBeUndefined();
-
- await stagehand.close();
- });
-});
diff --git a/evals/deterministic/tests/page/waitFor.test.ts b/evals/deterministic/tests/page/waitFor.test.ts
deleted file mode 100644
index e42dd3b58..000000000
--- a/evals/deterministic/tests/page/waitFor.test.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-import { test, expect } from "@playwright/test";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/evals/deterministic/stagehand.config";
-
-test.describe("StagehandPage - waitFor", () => {
- test("should wait for an element to become visible", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://docs.browserbase.com/introduction");
- const dynamicElement = page.locator(
- "div.grid:nth-child(1) > a:nth-child(1) > div:nth-child(1)",
- );
-
- const isVisibleBefore = await dynamicElement.isVisible();
- expect(isVisibleBefore).toBe(false);
-
- const clickableElement = page.locator(
- "div.not-prose:nth-child(2) > a:nth-child(1) > div:nth-child(1)",
- );
- await clickableElement.click();
-
- await dynamicElement.waitFor({ state: "visible" });
-
- const isVisibleAfter = await dynamicElement.isVisible();
- expect(isVisibleAfter).toBe(true);
-
- await stagehand.close();
- });
-
- test("should wait for an element to be detached", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://docs.browserbase.com/introduction");
-
- const disappearingElement = page.locator(
- "div.not-prose:nth-child(2) > a:nth-child(1) > div:nth-child(1)",
- );
-
- await disappearingElement.click();
- await disappearingElement.waitFor({ state: "detached" });
-
- const isAttachedAfter = await disappearingElement.isVisible();
- expect(isAttachedAfter).toBe(false);
-
- await stagehand.close();
- });
-
- test("should wait for a specific event (waitForEvent)", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://docs.browserbase.com/introduction");
-
- const consolePromise = page.waitForEvent("console");
- await page.evaluate(() => {
- console.log("Hello from the browser console!");
- });
- const consoleMessage = await consolePromise;
- expect(consoleMessage.text()).toBe("Hello from the browser console!");
-
- await stagehand.close();
- });
-
- test("should wait for a function to return true (waitForFunction)", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://docs.browserbase.com/introduction");
-
- await page.evaluate(() => {
- setTimeout(() => {
- const w = window as typeof window & {
- __stagehandFlag?: boolean;
- };
- w.__stagehandFlag = true;
- }, 1000);
- });
-
- await page.waitForFunction(() => {
- const w = window as typeof window & {
- __stagehandFlag?: boolean;
- };
- return w.__stagehandFlag === true;
- });
-
- const value = await page.evaluate(() => {
- const w = window as typeof window & {
- __stagehandFlag?: boolean;
- };
- return w.__stagehandFlag;
- });
- expect(value).toBe(true);
-
- await stagehand.close();
- });
-
- test("should wait for the load state (waitForLoadState)", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://docs.browserbase.com/introduction");
- await page.waitForLoadState("networkidle");
- const heroTitle = page.locator("h1");
- await expect(heroTitle).toHaveText(/Documentation/i);
-
- await stagehand.close();
- });
-
- test("should wait for a specific request (waitForRequest)", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- const requestPromise = page.waitForRequest((req) =>
- req.url().includes("mintlify"),
- );
-
- await page.goto("https://docs.browserbase.com/introduction");
- const matchingRequest = await requestPromise;
- expect(matchingRequest.url()).toContain("mintlify");
-
- await stagehand.close();
- });
-
- test("should wait for a specific response (waitForResponse)", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- const responsePromise = page.waitForResponse(
- (res) => res.url().includes("introduction") && res.status() === 200,
- );
-
- await page.goto("https://docs.browserbase.com/introduction");
- const matchingResponse = await responsePromise;
- expect(await matchingResponse.text()).toContain("Browserbase");
-
- await stagehand.close();
- });
-
- test("should wait for a URL (waitForURL)", async () => {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = stagehand.page;
- await page.goto("https://docs.browserbase.com");
-
- const getStartedLink = page.locator(
- "div.not-prose:nth-child(3) > a:nth-child(1) > div:nth-child(1)",
- );
- await getStartedLink.click();
-
- await page.waitForURL(/.*getting-started.*/);
- expect(page.url()).toContain("/getting-started");
-
- await stagehand.close();
- });
-});
diff --git a/evals/env.ts b/evals/env.ts
deleted file mode 100644
index 45f877bd1..000000000
--- a/evals/env.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Determine the current environment in which the evaluations are running:
- * - BROWSERBASE or LOCAL
- *
- * The environment is read from the EVAL_ENV environment variable.
- */
-export const env: "BROWSERBASE" | "LOCAL" =
- process.env.EVAL_ENV?.toLowerCase() === "browserbase"
- ? "BROWSERBASE"
- : "LOCAL";
-
-/**
- * Enable or disable caching based on the EVAL_ENABLE_CACHING environment variable.
- * Caching may improve performance by not re-fetching or re-computing certain results.
- * By default, caching is disabled unless explicitly enabled.
- */
-export const enableCaching =
- process.env.EVAL_ENABLE_CACHING?.toLowerCase() === "true";
diff --git a/evals/evals.config.json b/evals/evals.config.json
deleted file mode 100644
index ade809c32..000000000
--- a/evals/evals.config.json
+++ /dev/null
@@ -1,322 +0,0 @@
-{
- "tasks": [
- {
- "name": "history",
- "categories": ["combination"]
- },
- {
- "name": "expect_act_timeout",
- "categories": ["regression"]
- },
- {
- "name": "extract_repo_name",
- "categories": ["extract"]
- },
- {
- "name": "amazon_add_to_cart",
- "categories": ["act"]
- },
- {
- "name": "instructions",
- "categories": ["regression", "combination"]
- },
- {
- "name": "bidnet",
- "categories": ["act"]
- },
- {
- "name": "ionwave",
- "categories": ["act", "regression"],
- "extract_method": "domExtract"
- },
- {
- "name": "nonsense_action",
- "categories": ["act"]
- },
- {
- "name": "peeler_simple",
- "categories": ["act"]
- },
- {
- "name": "simple_google_search",
- "categories": ["act"]
- },
- {
- "name": "vantechjournal",
- "categories": ["act"]
- },
- {
- "name": "wikipedia",
- "categories": ["act"]
- },
-
- {
- "name": "allrecipes",
- "categories": ["combination"]
- },
- {
- "name": "arxiv",
- "categories": ["combination"]
- },
- {
- "name": "extract_collaborators",
- "categories": ["combination"]
- },
- {
- "name": "extract_github_commits",
- "categories": ["combination"]
- },
- {
- "name": "imdb_movie_details",
- "categories": ["combination"]
- },
- {
- "name": "peeler_complex",
- "categories": ["combination"]
- },
- {
- "name": "sciquest",
- "categories": ["combination"]
- },
- {
- "name": "wichita",
- "categories": ["combination", "regression"],
- "extract_method": "domExtract"
- },
- {
- "name": "hn_aisdk",
- "categories": ["llm_clients"]
- },
- {
- "name": "hn_langchain",
- "categories": ["llm_clients"]
- },
- {
- "name": "hn_customOpenAI",
- "categories": ["llm_clients"]
- },
- {
- "name": "apple",
- "categories": ["experimental"]
- },
- {
- "name": "combination_sauce",
- "categories": ["experimental"]
- },
- {
- "name": "costar",
- "categories": ["experimental"]
- },
- {
- "name": "expedia",
- "categories": ["experimental"]
- },
- {
- "name": "expedia_search",
- "categories": ["experimental"]
- },
- {
- "name": "extract_aigrant_companies",
- "categories": ["text_extract", "regression"],
- "extract_method": "textExtract"
- },
- {
- "name": "extract_capacitor_info",
- "categories": ["experimental", "text_extract"]
- },
- {
- "name": "extract_partners",
- "categories": ["experimental"]
- },
- {
- "name": "extract_press_releases",
- "categories": ["experimental", "text_extract"]
- },
- {
- "name": "extract_snowshoeing_destinations",
- "categories": ["experimental", "text_extract"]
- },
- {
- "name": "google_jobs",
- "categories": ["experimental"]
- },
- {
- "name": "homedepot",
- "categories": ["experimental"]
- },
- {
- "name": "rakuten_jp",
- "categories": ["experimental"]
- },
- {
- "name": "stock_x",
- "categories": ["experimental"]
- },
- {
- "name": "ted_talk",
- "categories": ["experimental"]
- },
-
- {
- "name": "extract_baptist_health",
- "categories": ["extract"]
- },
- {
- "name": "extract_github_stars",
- "categories": ["extract"]
- },
- {
- "name": "extract_memorial_healthcare",
- "categories": ["extract", "regression"],
- "extract_method": "domExtract"
- },
- {
- "name": "extract_nhl_stats",
- "categories": ["extract"]
- },
- {
- "name": "extract_professional_info",
- "categories": ["extract"]
- },
- {
- "name": "extract_csa",
- "categories": ["text_extract"]
- },
- {
- "name": "extract_resistor_info",
- "categories": ["extract"]
- },
- {
- "name": "extract_rockauto",
- "categories": ["extract"]
- },
- {
- "name": "extract_staff_members",
- "categories": ["extract"]
- },
-
- {
- "name": "ionwave_observe",
- "categories": ["observe"]
- },
- {
- "name": "panamcs",
- "categories": ["observe"]
- },
- {
- "name": "vanta_h",
- "categories": ["experimental"]
- },
- {
- "name": "extract_area_codes",
- "categories": ["text_extract"]
- },
- {
- "name": "extract_public_notices",
- "categories": ["text_extract"]
- },
- {
- "name": "extract_jstor_news",
- "categories": ["text_extract"]
- },
- {
- "name": "extract_apartments",
- "categories": ["text_extract"]
- },
- {
- "name": "extract_zillow",
- "categories": ["text_extract"]
- },
- {
- "name": "observe_github",
- "categories": ["observe", "regression"],
- "extract_method": "textExtract"
- },
- {
- "name": "observe_vantechjournal",
- "categories": ["observe", "regression"],
- "extract_method": "textExtract"
- },
- {
- "name": "observe_amazon_add_to_cart",
- "categories": ["observe"]
- },
- {
- "name": "observe_simple_google_search",
- "categories": ["observe"]
- },
- {
- "name": "observe_yc_startup",
- "categories": ["observe"]
- },
- {
- "name": "observe_taxes",
- "categories": ["observe"]
- },
- {
- "name": "observe_iframes1",
- "categories": ["regression", "observe"]
- },
- {
- "name": "observe_iframes2",
- "categories": ["regression", "observe"]
- },
- {
- "name": "extract_hamilton_weather",
- "categories": ["targeted_extract", "regression"],
- "extract_method": "textExtract"
- },
- {
- "name": "extract_regulations_table",
- "categories": ["targeted_extract"]
- },
- {
- "name": "extract_recipe",
- "categories": ["targeted_extract"]
- },
- {
- "name": "extract_aigrant_targeted",
- "categories": ["targeted_extract"]
- },
- {
- "name": "extract_aigrant_targeted_2",
- "categories": ["targeted_extract"]
- },
- {
- "name": "extract_geniusee",
- "categories": ["targeted_extract"]
- },
- {
- "name": "extract_geniusee_2",
- "categories": ["targeted_extract"]
- },
- {
- "name": "scroll_50",
- "categories": ["regression", "act"]
- },
- {
- "name": "scroll_75",
- "categories": ["regression", "act"]
- },
- {
- "name": "nextChunk",
- "categories": ["regression", "act"]
- },
- {
- "name": "prevChunk",
- "categories": ["regression", "act"]
- },
- {
- "name": "google_flights",
- "categories": ["act"]
- },
- {
- "name": "extract_jfk_links",
- "categories": ["extract"]
- },
- {
- "name": "extract_single_link",
- "categories": ["extract"]
- }
- ]
-}
diff --git a/evals/index.eval.ts b/evals/index.eval.ts
deleted file mode 100644
index 617214e1e..000000000
--- a/evals/index.eval.ts
+++ /dev/null
@@ -1,390 +0,0 @@
-/**
- * This script orchestrates the running of evaluations against a set of tasks.
- * It uses Braintrust to run multiple testcases (each testcase representing a
- * given task-model combination) and then aggregates the results, producing
- * a summary of passes, failures, and categorized success rates.
- *
- * Overview:
- * - Reads a configuration file `evals.config.json` to determine what tasks (evaluations)
- * are available and which categories they belong to.
- * - Supports filtering which tasks to run either by evaluation category or by specific task name.
- * - Supports multiple models, defaulting to certain sets of models depending on the category.
- * - Runs each selected task against each selected model in parallel, collecting results.
- * - Saves a summary of the evaluation results to `eval-summary.json`.
- */
-import fs from "fs";
-import path from "path";
-import process from "process";
-import {
- DEFAULT_EVAL_CATEGORIES,
- filterByCategory,
- filterByEvalName,
- useTextExtract,
-} from "./args";
-import { generateExperimentName } from "./utils";
-import { exactMatch, errorMatch } from "./scoring";
-import { tasksByName, MODELS, tasksConfig } from "./taskConfig";
-import { Eval, wrapAISDKModel, wrapOpenAI } from "braintrust";
-import { EvalFunction, SummaryResult, Testcase } from "@/types/evals";
-import { EvalLogger } from "./logger";
-import { AvailableModel, LLMClient } from "@/dist";
-import { env } from "./env";
-import dotenv from "dotenv";
-import { StagehandEvalError } from "@/types/stagehandErrors";
-import { CustomOpenAIClient } from "@/examples/external_clients/customOpenAI";
-import OpenAI from "openai";
-import { initStagehand } from "./initStagehand";
-import { AISdkClient } from "@/examples/external_clients/aisdk";
-import { google } from "@ai-sdk/google";
-import { anthropic } from "@ai-sdk/anthropic";
-import { groq } from "@ai-sdk/groq";
-import { cerebras } from "@ai-sdk/cerebras";
-dotenv.config();
-
-/**
- * Read max concurrency and trial count from environment variables set in args.ts.
- * Fallback to defaults (20 and 5) if they're not provided.
- */
-const MAX_CONCURRENCY = process.env.EVAL_MAX_CONCURRENCY
- ? parseInt(process.env.EVAL_MAX_CONCURRENCY, 10)
- : 3;
-
-const TRIAL_COUNT = process.env.EVAL_TRIAL_COUNT
- ? parseInt(process.env.EVAL_TRIAL_COUNT, 10)
- : 3;
-
-/**
- * generateSummary:
- * After all evaluations have finished, aggregate the results into a summary.
- * This summary includes:
- * - Which tasks passed or failed (with model and categories).
- * - Category-wise success percentages.
- * - Model-wise success percentages.
- *
- * The summary is written to `eval-summary.json` for further analysis.
- */
-const generateSummary = async (
- results: SummaryResult[],
- experimentName: string,
-) => {
- // Determine passed testcases (those with _success: true)
- const passed = results
- .filter((r) => r.output._success)
- .map((r) => ({
- eval: r.input.name,
- model: r.input.modelName,
- categories: tasksByName[r.input.name].categories,
- }));
-
- // Determine failed testcases (those with _success: false)
- const failed = results
- .filter((r) => !r.output._success)
- .map((r) => ({
- eval: r.input.name,
- model: r.input.modelName,
- categories: tasksByName[r.input.name].categories,
- }));
-
- // Calculate success counts for each category
- const categorySuccessCounts: Record<
- string,
- { total: number; success: number }
- > = {};
- for (const taskName of Object.keys(tasksByName)) {
- const taskCategories = tasksByName[taskName].categories;
- const taskResults = results.filter((r) => r.input.name === taskName);
- const successCount = taskResults.filter((r) => r.output._success).length;
-
- for (const cat of taskCategories) {
- if (!categorySuccessCounts[cat]) {
- categorySuccessCounts[cat] = { total: 0, success: 0 };
- }
- categorySuccessCounts[cat].total += taskResults.length;
- categorySuccessCounts[cat].success += successCount;
- }
- }
-
- // Compute percentage success per category
- const categories: Record = {};
- for (const [cat, counts] of Object.entries(categorySuccessCounts)) {
- categories[cat] = Math.round((counts.success / counts.total) * 100);
- }
-
- // Compute percentage success per model
- const models: Record = {};
- const allModels = [...new Set(results.map((r) => r.input.modelName))];
- for (const model of allModels) {
- const modelResults = results.filter((r) => r.input.modelName === model);
- const successCount = modelResults.filter((r) => r.output._success).length;
- models[model] = Math.round((successCount / modelResults.length) * 100);
- }
-
- // Format and write the summary to a JSON file
- const formattedSummary = {
- experimentName,
- passed,
- failed,
- categories,
- models,
- };
-
- fs.writeFileSync(
- "eval-summary.json",
- JSON.stringify(formattedSummary, null, 2),
- );
- console.log("Evaluation summary written to eval-summary.json");
-};
-
-/**
- * generateFilteredTestcases:
- * Based on the chosen filters (category or specific eval name) and environment,
- * this function generates the set of testcases to run. Each testcase is a combination
- * of a task and a model.
- *
- * Steps:
- * - Start with all combinations of tasks (from `tasksByName`) and models (`MODELS`).
- * - Filter by category if a category filter was specified.
- * - Filter by evaluation name if specified.
- * - In the BROWSERBASE environment, exclude certain tasks that are not suitable.
- */
-const generateFilteredTestcases = (): Testcase[] => {
- // Create a list of all testcases for each model-task combination.
- let allTestcases = MODELS.flatMap((model) =>
- Object.keys(tasksByName).map((testName) => ({
- input: { name: testName, modelName: model },
- name: testName,
- tags: [
- model,
- testName,
- ...(tasksConfig.find((t) => t.name === testName)?.categories || []).map(
- (x) => `category/${x}`,
- ),
- ],
- metadata: {
- model,
- test: testName,
- categories: tasksConfig.find((t) => t.name === testName)?.categories,
- },
- expected: true,
- })),
- );
-
- // Filter test cases to match default eval categories
- allTestcases = allTestcases.filter((testcase) =>
- DEFAULT_EVAL_CATEGORIES.some((category) =>
- tasksByName[testcase.name].categories.includes(category),
- ),
- );
-
- // Filter by category if a category is specified
- if (filterByCategory) {
- allTestcases = allTestcases.filter((testcase) =>
- tasksByName[testcase.name].categories.includes(filterByCategory!),
- );
- }
-
- // Filter by a specific evaluation (task) name if specified
- if (filterByEvalName) {
- allTestcases = allTestcases.filter(
- (testcase) =>
- testcase.name === filterByEvalName ||
- testcase.input.name === filterByEvalName,
- );
- }
-
- // If running in BROWSERBASE environment, exclude tasks that are not applicable.
- if (env === "BROWSERBASE") {
- allTestcases = allTestcases.filter(
- (testcase) => !["peeler_simple", "stock_x"].includes(testcase.name),
- );
- }
-
- console.log(
- "All test cases:",
- allTestcases
- .map(
- (t, i) =>
- `${i}: ${t.name} (${t.input.modelName}): ${t.metadata.categories}`,
- )
- .join("\n"),
- );
-
- return allTestcases;
-};
-
-/**
- * Main execution block:
- * - Determine experiment name
- * - Determine the project name (braintrustProjectName) based on CI or dev environment
- * - Run the Eval function with the given configuration:
- * * experimentName: A label for this run
- * * data: A function that returns the testcases to run
- * * task: A function that executes each task, given input specifying model and task name
- * * scores: An array of scoring functions
- * * maxConcurrency: Limit on parallel tasks
- * * trialCount: Number of trials (retries) per task
- * - Collect and summarize results using `generateSummary`.
- */
-(async () => {
- // Generate a unique name for the experiment
- const experimentName: string = generateExperimentName({
- evalName: filterByEvalName || undefined,
- category: filterByCategory || undefined,
- environment: env,
- });
-
- // Determine braintrust project name to use (stagehand in CI, stagehand-dev otherwise)
- const braintrustProjectName =
- process.env.CI === "true" ? "stagehand" : "stagehand-dev";
-
- try {
- // Run the evaluations with the braintrust Eval function
- const evalResult = await Eval(braintrustProjectName, {
- experimentName,
- data: generateFilteredTestcases,
- // Each test is a function that runs the corresponding task module
- task: async (input: { name: string; modelName: AvailableModel }) => {
- const logger = new EvalLogger();
- try {
- // Dynamically import the task based on its name
- const taskModulePath = path.join(
- __dirname,
- "tasks",
- `${input.name}.ts`,
- );
- const taskModule = (await import(taskModulePath)) as {
- [key: string]: EvalFunction;
- };
- const taskFunction = taskModule[input.name];
-
- if (typeof taskFunction !== "function") {
- throw new StagehandEvalError(
- `No Eval function found for task name: ${input.name}`,
- );
- }
- let shouldUseTextExtract = useTextExtract;
- const categories = tasksByName[input.name].categories || [];
- const isRegression = categories.includes("regression");
- const regressionExtractMethod = tasksByName[input.name].extractMethod;
- if (isRegression) {
- if (regressionExtractMethod) {
- shouldUseTextExtract = regressionExtractMethod === "textExtract";
- }
- }
-
- // Execute the task
- let llmClient: LLMClient;
- if (input.modelName.startsWith("gpt")) {
- llmClient = new CustomOpenAIClient({
- modelName: input.modelName as AvailableModel,
- client: wrapOpenAI(
- new OpenAI({
- apiKey: process.env.OPENAI_API_KEY,
- }),
- ),
- });
- } else if (input.modelName.startsWith("gemini")) {
- llmClient = new AISdkClient({
- model: wrapAISDKModel(google(input.modelName)),
- });
- } else if (input.modelName.startsWith("claude")) {
- llmClient = new AISdkClient({
- model: wrapAISDKModel(anthropic(input.modelName)),
- });
- } else if (input.modelName.includes("groq")) {
- llmClient = new AISdkClient({
- model: wrapAISDKModel(
- groq(
- input.modelName.substring(input.modelName.indexOf("/") + 1),
- ),
- ),
- });
- } else if (input.modelName.includes("cerebras")) {
- llmClient = new AISdkClient({
- model: wrapAISDKModel(
- cerebras(
- input.modelName.substring(input.modelName.indexOf("/") + 1),
- ),
- ),
- });
- } else if (input.modelName.includes("/")) {
- llmClient = new CustomOpenAIClient({
- modelName: input.modelName as AvailableModel,
- client: wrapOpenAI(
- new OpenAI({
- apiKey: process.env.TOGETHER_AI_API_KEY,
- baseURL: "https://api.together.xyz/v1",
- }),
- ),
- });
- }
- const taskInput = await initStagehand({
- logger,
- llmClient,
- useTextExtract: shouldUseTextExtract,
- });
- let result;
- try {
- result = await taskFunction(taskInput);
- // Log result to console
- if (result && result._success) {
- console.log(`✅ ${input.name}: Passed`);
- } else {
- console.log(`❌ ${input.name}: Failed`);
- }
- } finally {
- await taskInput.stagehand.close();
- }
- return result;
- } catch (error) {
- // Log any errors that occur during task execution
- console.error(`❌ ${input.name}: Error - ${error}`);
- logger.error({
- message: `Error in task ${input.name}`,
- level: 0,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: JSON.parse(JSON.stringify(error, null, 2)),
- logs: logger.getLogs(),
- };
- }
- },
- // Use the scoring functions defined above
- scores: [exactMatch, errorMatch],
- maxConcurrency: MAX_CONCURRENCY,
- trialCount: TRIAL_COUNT,
- });
-
- // Map results to the SummaryResult format
- const summaryResults: SummaryResult[] = evalResult.results.map((result) => {
- const output =
- typeof result.output === "boolean"
- ? { _success: result.output }
- : result.output;
-
- return {
- input: result.input,
- output,
- name: result.input.name,
- score: output._success ? 1 : 0,
- };
- });
-
- // Generate and write the summary
- await generateSummary(summaryResults, experimentName);
- } catch (error) {
- console.error("Error during evaluation run:", error);
- process.exit(1);
- }
-})();
diff --git a/evals/initStagehand.ts b/evals/initStagehand.ts
deleted file mode 100644
index 174778aea..000000000
--- a/evals/initStagehand.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * This file provides a function to initialize a Stagehand instance for use in evaluations.
- * It configures the Stagehand environment and sets default options based on the current environment
- * (e.g., local or BROWSERBASE), caching preferences, and verbosity. It also establishes a logger for
- * capturing logs emitted by Stagehand.
- *
- * We create a central config object (`StagehandConfig`) that defines all parameters for Stagehand.
- *
- * The `initStagehand` function takes the model name, an optional DOM settling timeout, and an EvalLogger,
- * then uses these to override some default values before creating and initializing the Stagehand instance.
- */
-
-import { enableCaching, env } from "./env";
-import { ConstructorParams, LLMClient, Stagehand } from "@/dist";
-import { EvalLogger } from "./logger";
-import type { StagehandInitResult } from "@/types/evals";
-
-/**
- * StagehandConfig:
- * This configuration object follows a similar pattern to `examples/stagehand.config.ts`.
- * It sets the environment, verbosity, caching preferences, and other defaults. Some values,
- * like `apiKey` and `projectId`, can be defined via environment variables if needed.
- *
- * Adjust or remove fields as appropriate for your environment.
- */
-const StagehandConfig = {
- env: env,
- apiKey: process.env.BROWSERBASE_API_KEY,
- projectId: process.env.BROWSERBASE_PROJECT_ID,
- verbose: 2 as const,
- debugDom: true,
- headless: false,
- enableCaching,
- domSettleTimeoutMs: 30_000,
- disablePino: true,
-};
-
-/**
- * Initializes a Stagehand instance for a given model:
- * - modelName: The model to use (overrides default in StagehandConfig)
- * - domSettleTimeoutMs: Optional timeout for DOM settling operations
- * - logger: An EvalLogger instance for capturing logs
- *
- * Returns:
- * - stagehand: The initialized Stagehand instance
- * - logger: The provided logger, associated with the Stagehand instance
- * - initResponse: Any response data returned by Stagehand initialization
- */
-export const initStagehand = async ({
- llmClient,
- domSettleTimeoutMs,
- logger,
- configOverrides,
- actTimeoutMs,
- useTextExtract,
-}: {
- llmClient: LLMClient;
- domSettleTimeoutMs?: number;
- logger: EvalLogger;
- configOverrides?: Partial;
- actTimeoutMs?: number;
- useTextExtract?: boolean;
-}): Promise => {
- const config = {
- ...StagehandConfig,
- llmClient,
- ...(domSettleTimeoutMs && { domSettleTimeoutMs }),
- actTimeoutMs,
- ...configOverrides,
- logger: logger.log.bind(logger),
- };
-
- const stagehand = new Stagehand(config);
-
- // Associate the logger with the Stagehand instance
- logger.init(stagehand);
-
- const { debugUrl, sessionUrl } = await stagehand.init();
- return {
- stagehand,
- stagehandConfig: config,
- logger,
- debugUrl,
- sessionUrl,
- useTextExtract,
- };
-};
diff --git a/evals/llm_clients/hn_aisdk.ts b/evals/llm_clients/hn_aisdk.ts
deleted file mode 100644
index 51ab4937e..000000000
--- a/evals/llm_clients/hn_aisdk.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { Stagehand } from "@/dist";
-import { AISdkClient } from "@/examples/external_clients/aisdk";
-import { EvalFunction } from "@/types/evals";
-import { openai } from "@ai-sdk/openai/dist";
-import { z } from "zod";
-
-export const hn_aisdk: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehandConfig,
- logger,
-}) => {
- const stagehand = new Stagehand({
- ...stagehandConfig,
- llmClient: new AISdkClient({
- model: openai("gpt-4o-mini"),
- }),
- });
- await stagehand.init();
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/hackernews/",
- );
-
- let { story } = await stagehand.page.extract({
- instruction: "extract the title of the top story on the page",
- schema: z.object({
- story: z.string().describe("the title of the top story on the page"),
- }),
- });
- // remove the (url) part of the story title
- story = story.split(" (")[0];
-
- const expectedStoryElement = await stagehand.page.$(
- "xpath=/html/body/center/table/tbody/tr[3]/td/table/tbody/tr[1]/td[3]/span/a",
- );
- // remove the (url) part of the story title
- const expectedStory = (await expectedStoryElement?.textContent())?.split(
- " (",
- )?.[0];
-
- if (!expectedStory) {
- logger.error({
- message: "Could not find expected story element",
- level: 0,
- });
- return {
- _success: false,
- error: "Could not find expected story element",
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- if (story !== expectedStory) {
- logger.error({
- message: "Extracted story does not match expected story",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedStory,
- type: "string",
- },
- actual: {
- value: story,
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Extracted story does not match expected story",
- expectedStory,
- actualStory: story,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- await stagehand.page.act("Click on the 'new' tab");
-
- if (stagehand.page.url() !== "https://news.ycombinator.com/newest") {
- logger.error({
- message: "Page did not navigate to the 'new' tab",
- level: 0,
- auxiliary: {
- expected: {
- value: "https://news.ycombinator.com/newest",
- type: "string",
- },
- actual: {
- value: stagehand.page.url(),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Page did not navigate to the 'new' tab",
- expectedUrl: "https://news.ycombinator.com/newest",
- actualUrl: stagehand.page.url(),
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- await stagehand.close();
-
- return {
- _success: true,
- expectedStory,
- actualStory: story,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/llm_clients/hn_customOpenAI.ts b/evals/llm_clients/hn_customOpenAI.ts
deleted file mode 100644
index 7b4f1f1e5..000000000
--- a/evals/llm_clients/hn_customOpenAI.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-import { CustomOpenAIClient } from "@/examples/external_clients/customOpenAI";
-import OpenAI from "openai";
-import { Stagehand } from "@/dist";
-
-export const hn_customOpenAI: EvalFunction = async ({
- logger,
- stagehandConfig,
- debugUrl,
- sessionUrl,
-}) => {
- const stagehand = new Stagehand({
- ...stagehandConfig,
- llmClient: new CustomOpenAIClient({
- modelName: "gpt-4o-mini",
- client: new OpenAI({
- apiKey: process.env.OPENAI_API_KEY,
- }),
- }),
- });
-
- await stagehand.init();
-
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/hackernews/",
- );
-
- let { story } = await stagehand.page.extract({
- instruction: "extract the title of the top story on the page",
- schema: z.object({
- story: z.string().describe("the title of the top story on the page"),
- }),
- });
- // remove the (url) part of the story title
- story = story.split(" (")[0];
-
- const expectedStoryElement = await stagehand.page.$(
- "xpath=/html/body/center/table/tbody/tr[3]/td/table/tbody/tr[1]/td[3]/span/a",
- );
- // remove the (url) part of the story title
- const expectedStory = (await expectedStoryElement?.textContent())?.split(
- " (",
- )?.[0];
-
- if (!expectedStory) {
- logger.error({
- message: "Could not find expected story element",
- level: 0,
- });
- return {
- _success: false,
- error: "Could not find expected story element",
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- if (story !== expectedStory) {
- logger.error({
- message: "Extracted story does not match expected story",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedStory,
- type: "string",
- },
- actual: {
- value: story,
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Extracted story does not match expected story",
- expectedStory,
- actualStory: story,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- await stagehand.page.act("Click on the 'new' tab");
-
- if (stagehand.page.url() !== "https://news.ycombinator.com/newest") {
- logger.error({
- message: "Page did not navigate to the 'new' tab",
- level: 0,
- auxiliary: {
- expected: {
- value: "https://news.ycombinator.com/newest",
- type: "string",
- },
- actual: {
- value: stagehand.page.url(),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Page did not navigate to the 'new' tab",
- expectedUrl: "https://news.ycombinator.com/newest",
- actualUrl: stagehand.page.url(),
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- await stagehand.close();
-
- return {
- _success: true,
- expectedStory,
- actualStory: story,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/llm_clients/hn_langchain.ts b/evals/llm_clients/hn_langchain.ts
deleted file mode 100644
index 35fc6b68d..000000000
--- a/evals/llm_clients/hn_langchain.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-import { LangchainClient } from "@/examples/external_clients/langchain";
-import { ChatOpenAI } from "@langchain/openai";
-import { Stagehand } from "@/dist";
-
-export const hn_langchain: EvalFunction = async ({
- logger,
- stagehandConfig,
- debugUrl,
- sessionUrl,
-}) => {
- const stagehand = new Stagehand({
- ...stagehandConfig,
- llmClient: new LangchainClient(
- new ChatOpenAI({
- model: "gpt-4o-mini",
- }),
- ),
- });
- await stagehand.init();
-
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/hackernews/",
- );
-
- let { story } = await stagehand.page.extract({
- instruction: "extract the title of the top story on the page",
- schema: z.object({
- story: z.string().describe("the title of the top story on the page"),
- }),
- });
- // remove the (url) part of the story title
- story = story.split(" (")[0];
-
- const expectedStoryElement = await stagehand.page.$(
- "xpath=/html/body/center/table/tbody/tr[3]/td/table/tbody/tr[1]/td[3]/span/a",
- );
- // remove the (url) part of the story title
- const expectedStory = (await expectedStoryElement?.textContent())?.split(
- " (",
- )?.[0];
-
- if (!expectedStory) {
- logger.error({
- message: "Could not find expected story element",
- level: 0,
- });
- return {
- _success: false,
- error: "Could not find expected story element",
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- if (story !== expectedStory) {
- logger.error({
- message: "Extracted story does not match expected story",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedStory,
- type: "string",
- },
- actual: {
- value: story,
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Extracted story does not match expected story",
- expectedStory,
- actualStory: story,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- await stagehand.page.act("Click on the 'new' tab");
-
- if (stagehand.page.url() !== "https://news.ycombinator.com/newest") {
- logger.error({
- message: "Page did not navigate to the 'new' tab",
- level: 0,
- auxiliary: {
- expected: {
- value: "https://news.ycombinator.com/newest",
- type: "string",
- },
- actual: {
- value: stagehand.page.url(),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Page did not navigate to the 'new' tab",
- expectedUrl: "https://news.ycombinator.com/newest",
- actualUrl: stagehand.page.url(),
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- await stagehand.close();
-
- return {
- _success: true,
- expectedStory,
- actualStory: story,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/allrecipes.ts b/evals/tasks/allrecipes.ts
deleted file mode 100644
index e1b8a0c50..000000000
--- a/evals/tasks/allrecipes.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const allrecipes: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto("https://www.allrecipes.com/", {
- waitUntil: "domcontentloaded",
- });
-
- await stagehand.page.act({
- action: 'Type "chocolate chip cookies" in the search bar',
- });
- await stagehand.page.act({
- action: "press enter",
- });
-
- const recipeDetails = await stagehand.page.extract({
- instruction:
- "Extract the title of the first recipe and the total number of ratings it has received.",
- schema: z.object({
- title: z.string().describe("Title of the recipe"),
- total_ratings: z
- .string()
- .describe("Total number of ratings for the recipe"),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const { title, total_ratings } = recipeDetails;
- const expectedTitle = "Best Chocolate Chip Cookies";
- const expectedRatings = 19164;
-
- const extractedRatings = parseInt(total_ratings.replace(/[^\d]/g, ""), 10);
- const isRatingsWithinRange =
- extractedRatings >= expectedRatings - 1000 &&
- extractedRatings <= expectedRatings + 1000;
-
- if (title !== expectedTitle || !isRatingsWithinRange) {
- const errors = [];
- if (title !== expectedTitle) {
- errors.push({
- message: "Extracted title does not match the expected title",
- expected: expectedTitle,
- actual: title,
- });
- }
- if (!isRatingsWithinRange) {
- errors.push({
- message: "Extracted ratings are not within the expected range",
- expected: `${expectedRatings} ± 1000`,
- actual: extractedRatings.toString(),
- });
- }
-
- logger.error({
- message: "Failed to extract correct recipe details",
- level: 0,
- auxiliary: {
- errors: {
- value: JSON.stringify(errors),
- type: "object",
- },
- },
- });
-
- return {
- _success: false,
- error: "Recipe details extraction validation failed",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- recipeDetails: {
- title,
- total_ratings: extractedRatings,
- },
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/amazon_add_to_cart.ts b/evals/tasks/amazon_add_to_cart.ts
deleted file mode 100644
index 64c410f8f..000000000
--- a/evals/tasks/amazon_add_to_cart.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const amazon_add_to_cart: EvalFunction = async ({
- logger,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/amazon/",
- );
-
- await stagehand.page.waitForTimeout(5000);
-
- await stagehand.page.act({
- action: "click the 'Add to Cart' button",
- });
-
- await stagehand.page.waitForTimeout(2000);
-
- await stagehand.page.act({
- action: "click the 'Proceed to checkout' button",
- });
-
- await stagehand.page.waitForTimeout(2000);
- const currentUrl = stagehand.page.url();
- const expectedUrl =
- "https://browserbase.github.io/stagehand-eval-sites/sites/amazon/sign-in.html";
-
- await stagehand.close();
-
- return {
- _success: currentUrl === expectedUrl,
- currentUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/apple.ts b/evals/tasks/apple.ts
deleted file mode 100644
index 1926a1718..000000000
--- a/evals/tasks/apple.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const apple: EvalFunction = async ({
- logger,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto("https://www.apple.com/iphone-16-pro/");
-
- await stagehand.page.act({ action: "click on the buy button" });
- await stagehand.page.act({ action: "select the Pro Max model" });
- await stagehand.page.act({ action: "select the natural titanium color" });
- await stagehand.page.act({ action: "select the 256GB storage option" });
- await stagehand.page.act({
- action: "click on the 'select a smartphone' trade-in option",
- });
-
- await stagehand.page.act({
- action: "select the iPhone 13 mini model from the dropdown",
- });
- await stagehand.page.act({
- action: "select the iPhone 13 mini is in good condition",
- });
-
- const successMessageLocator = stagehand.page.locator(
- 'text="Good News. Your iPhone 13 mini qualifies for credit."',
- );
- const isVisible = await successMessageLocator.isVisible();
-
- await stagehand.close();
-
- return {
- _success: isVisible,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/bidnet.ts b/evals/tasks/bidnet.ts
deleted file mode 100644
index d73c83030..000000000
--- a/evals/tasks/bidnet.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const bidnet: EvalFunction = async ({
- logger,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto("https://www.bidnetdirect.com/");
-
- await stagehand.page.act({
- action: 'Click on the "Construction" keyword',
- });
-
- const expectedUrl =
- "https://www.bidnetdirect.com/public/solicitations/open?keywords=Construction";
- const currentUrl = stagehand.page.url();
-
- await stagehand.close();
-
- return {
- _success: currentUrl.startsWith(expectedUrl),
- currentUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/combination_sauce.ts b/evals/tasks/combination_sauce.ts
deleted file mode 100644
index fc8bfc32e..000000000
--- a/evals/tasks/combination_sauce.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const combination_sauce: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- try {
- await stagehand.page.goto("https://www.saucedemo.com/");
-
- const { usernames, password } = await stagehand.page.extract({
- instruction: "extract the accepted usernames and the password for login",
- schema: z.object({
- usernames: z.array(z.string()).describe("the accepted usernames"),
- password: z.string().describe("the password for login"),
- }),
- useTextExtract,
- });
-
- await stagehand.page.act({
- action: `enter username 'standard_user'`,
- });
-
- await stagehand.page.act({
- action: `enter password '${password}'`,
- });
-
- await stagehand.page.act({
- action: "click on 'login'",
- });
-
- const observations = await stagehand.page.observe({
- instruction: "find all the 'add to cart' buttons",
- });
-
- console.log("observations", observations);
- console.log("observations length", observations.length);
-
- const url = await stagehand.page.url();
-
- await stagehand.close();
-
- const usernamesCheck = usernames.length === 6;
- const urlCheck = url === "https://www.saucedemo.com/inventory.html";
- const observationsCheck = observations.length === 6;
-
- return {
- _success: usernamesCheck && urlCheck && observationsCheck,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- } catch (error) {
- console.error("Error or timeout occurred:", error);
-
- await stagehand.close();
-
- return {
- _success: false,
- error: JSON.parse(JSON.stringify(error, null, 2)),
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-};
diff --git a/evals/tasks/expect_act_timeout.ts b/evals/tasks/expect_act_timeout.ts
deleted file mode 100644
index 0a6f45170..000000000
--- a/evals/tasks/expect_act_timeout.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const expect_act_timeout: EvalFunction = async ({
- logger,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto("https://docs.stagehand.dev");
- const result = await stagehand.page.act({
- action: "search for 'Stagehand'",
- timeoutMs: 1_000,
- });
-
- await stagehand.close();
-
- return {
- _success: !result.success,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/expedia.ts b/evals/tasks/expedia.ts
deleted file mode 100644
index ac5645a5d..000000000
--- a/evals/tasks/expedia.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const expedia: EvalFunction = async ({
- logger,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- try {
- await stagehand.page.goto("https://www.expedia.com/flights");
- await stagehand.page.act(
- "find round-trip flights from San Francisco (SFO) to Toronto (YYZ) for Jan 1, 2025 (up to one to two weeks)",
- );
- await stagehand.page.act("Go to the first non-stop flight");
- await stagehand.page.act("select the cheapest flight");
- await stagehand.page.act("click on the first non-stop flight");
- await stagehand.page.act("Take me to the checkout page");
-
- const url = stagehand.page.url();
- return {
- _success: url.startsWith("https://www.expedia.com/Checkout/"),
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- } catch (error) {
- logger.error({
- message: "Error in expedia eval",
- level: 0,
- auxiliary: {
- error: { value: error.message, type: "string" },
- trace: { value: error.stack, type: "string" },
- },
- });
-
- return {
- _success: false,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- } finally {
- await stagehand.close();
- }
-};
diff --git a/evals/tasks/expedia_search.ts b/evals/tasks/expedia_search.ts
deleted file mode 100644
index 592e553dd..000000000
--- a/evals/tasks/expedia_search.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const expedia_search: EvalFunction = async ({
- logger,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- try {
- await stagehand.page.goto("https://www.expedia.com/flights");
- await stagehand.page.act({
- action:
- "find round-trip flights from San Francisco (SFO) to Toronto (YYZ) for Jan 1, 2025 (up to one to two weeks)",
- });
-
- await stagehand.page.act({ action: "Go to the first non-stop flight" });
-
- await stagehand.page.act({ action: "select the cheapest flight" });
-
- await stagehand.page.act({ action: "click on the first non-stop flight" });
-
- await stagehand.page.act({
- action: "Take me to the checkout page",
- });
-
- const url = stagehand.page.url();
- return {
- _success: url.startsWith("https://www.expedia.com/Checkout/"),
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- } catch (error) {
- logger.error({
- message: `error in expedia function`,
- level: 0,
- auxiliary: {
- error: {
- value: JSON.stringify(error, null, 2),
- type: "object",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: JSON.parse(JSON.stringify(error, null, 2)),
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- } finally {
- await stagehand.close();
- }
-};
diff --git a/evals/tasks/extract_aigrant_companies.ts b/evals/tasks/extract_aigrant_companies.ts
deleted file mode 100644
index f3e1f19cb..000000000
--- a/evals/tasks/extract_aigrant_companies.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { z } from "zod";
-import { EvalFunction } from "@/types/evals";
-
-export const extract_aigrant_companies: EvalFunction = async ({
- logger,
- debugUrl,
- sessionUrl,
- stagehand,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/aigrant/",
- );
- const companyList = await stagehand.page.extract({
- instruction:
- "Extract all companies that received the AI grant and group them with their batch numbers as an array of objects. Each object should contain the company name and its corresponding batch number.",
- schema: z.object({
- companies: z.array(
- z.object({
- company: z.string(),
- batch: z.string(),
- }),
- ),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
- const companies = companyList.companies;
- const expectedLength = 91;
-
- const expectedFirstItem = {
- company: "Goodfire",
- batch: "4",
- };
-
- const expectedLastItem = {
- company: "Forefront",
- batch: "1",
- };
-
- if (companies.length !== expectedLength) {
- logger.error({
- message: "Incorrect number of companies extracted",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedLength.toString(),
- type: "integer",
- },
- actual: {
- value: companies.length.toString(),
- type: "integer",
- },
- },
- });
- return {
- _success: false,
- error: "Incorrect number of companies extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
- const firstItemMatches =
- companies[0].company === expectedFirstItem.company &&
- companies[0].batch === expectedFirstItem.batch;
-
- if (!firstItemMatches) {
- logger.error({
- message: "First company extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedFirstItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(companies[0]),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "First company extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const lastItemMatches =
- companies[companies.length - 1].company === expectedLastItem.company &&
- companies[companies.length - 1].batch === expectedLastItem.batch;
-
- if (!lastItemMatches) {
- logger.error({
- message: "Last company extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedLastItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(companies[companies.length - 1]),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "Last company extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_aigrant_targeted.ts b/evals/tasks/extract_aigrant_targeted.ts
deleted file mode 100644
index d72328505..000000000
--- a/evals/tasks/extract_aigrant_targeted.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { z } from "zod";
-import { EvalFunction } from "@/types/evals";
-
-export const extract_aigrant_targeted: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/aigrant/",
- );
- const selector = "/html/body/div/ul[5]/li[28]";
- const company = await stagehand.page.extract({
- instruction: "Extract the company name.",
- schema: z.object({
- company_name: z.string(),
- }),
- useTextExtract,
- selector: selector,
- });
-
- await stagehand.close();
- const companyName = company.company_name;
-
- const expectedName = {
- company_name: "Coframe",
- };
-
- const nameMatches = companyName == expectedName.company_name;
-
- if (!nameMatches) {
- logger.error({
- message: "extracted company name does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedName.company_name,
- type: "string",
- },
- actual: {
- value: companyName,
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Company name does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_aigrant_targeted_2.ts b/evals/tasks/extract_aigrant_targeted_2.ts
deleted file mode 100644
index 772d2f4a4..000000000
--- a/evals/tasks/extract_aigrant_targeted_2.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { z } from "zod";
-import { EvalFunction } from "@/types/evals";
-
-export const extract_aigrant_targeted_2: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/aigrant/",
- );
- const selector = "/html/body/div/ul[5]/li[28]";
- const company = await stagehand.page.extract({
- instruction: "Extract the name of the company that comes after 'Coframe'.",
- schema: z.object({
- company_name: z.string(),
- }),
- useTextExtract,
- selector: selector,
- });
-
- await stagehand.close();
- const companyName = company.company_name;
-
- // nameWeShouldNotGet matches the name of the company that comes after
- // CoFrame on the website. Since we are using targeted_extract here,
- // and passing in a selector that does NOT contain the nameWeShouldNotGet,
- // the LLM should have no visibility into what comes after 'CoFrame' if
- // targeted_extract is performing correctly
- const nameWeShouldNotGet = {
- company_name: "OpusClip",
- };
-
- const nameMatches = companyName == nameWeShouldNotGet.company_name;
-
- if (nameMatches) {
- logger.error({
- message:
- "extracted company name matches the company name that we SHOULD NOT get",
- level: 0,
- auxiliary: {
- expected: {
- value: nameWeShouldNotGet.company_name,
- type: "string",
- },
- actual: {
- value: companyName,
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error:
- "extracted company name matches the company name that we SHOULD NOT get",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_apartments.ts b/evals/tasks/extract_apartments.ts
deleted file mode 100644
index 96cfd06b7..000000000
--- a/evals/tasks/extract_apartments.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { z } from "zod";
-import { EvalFunction } from "../../types/evals";
-
-export const extract_apartments: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://www.apartments.com/san-francisco-ca/2-bedrooms/",
- );
- const apartment_listings = await stagehand.page.extract({
- instruction:
- "Extract all the apartment listings with their prices and their addresses.",
- schema: z.object({
- listings: z.array(
- z.object({
- price: z.string().describe("The price of the listing"),
- trails: z.string().describe("The address of the listing"),
- }),
- ),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
- const listings = apartment_listings.listings;
- const expectedLength = 40;
-
- if (listings.length < expectedLength) {
- logger.error({
- message: "Incorrect number of listings extracted",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedLength.toString(),
- type: "integer",
- },
- actual: {
- value: listings.length.toString(),
- type: "integer",
- },
- },
- });
- return {
- _success: false,
- error: "Incorrect number of listings extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_area_codes.ts b/evals/tasks/extract_area_codes.ts
deleted file mode 100644
index 07f99ec32..000000000
--- a/evals/tasks/extract_area_codes.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const extract_area_codes: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/ncc-area-codes/",
- { waitUntil: "domcontentloaded" },
- );
-
- const result = await stagehand.page.extract({
- instruction:
- "Extract ALL the Primary Center names and their corresponding Area Code, and the name of their corresponding Zone.",
- schema: z.object({
- primary_center_list: z.array(
- z.object({
- zone_name: z
- .string()
- .describe(
- "The name of the Zone that the Primary Center is in. For example, 'North Central Zone'.",
- ),
- primary_center_name: z
- .string()
- .describe(
- "The name of the Primary Center. I.e., this is the name of the city or town.",
- ),
- area_code: z
- .string()
- .describe(
- "The area code for the Primary Center. This will either be 2 or 3 digits.",
- ),
- }),
- ),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const primaryCenterList = result.primary_center_list;
- const expectedLength = 56;
-
- const expectedFirstItem = {
- zone_name: "Lagos Zone",
- primary_center_name: "Lagos",
- area_code: "01",
- };
-
- const expectedLastItem = {
- zone_name: "South-East",
- primary_center_name: "Yenagoa",
- area_code: "089",
- };
-
- if (primaryCenterList.length !== expectedLength) {
- logger.error({
- message: "Incorrect number of primary centers extracted",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedLength.toString(),
- type: "integer",
- },
- actual: {
- value: primaryCenterList.length.toString(),
- type: "integer",
- },
- },
- });
- return {
- _success: false,
- error: "Incorrect number of primary centers extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
- const firstItemMatches =
- primaryCenterList[0].zone_name === expectedFirstItem.zone_name &&
- primaryCenterList[0].primary_center_name ===
- expectedFirstItem.primary_center_name &&
- primaryCenterList[0].area_code === expectedFirstItem.area_code;
-
- if (!firstItemMatches) {
- logger.error({
- message: "First primary center extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedFirstItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(primaryCenterList[0]),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "First primary center extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const lastItemMatches =
- primaryCenterList[primaryCenterList.length - 1].zone_name ===
- expectedLastItem.zone_name &&
- primaryCenterList[primaryCenterList.length - 1].primary_center_name ===
- expectedLastItem.primary_center_name &&
- primaryCenterList[primaryCenterList.length - 1].area_code ===
- expectedLastItem.area_code;
-
- if (!lastItemMatches) {
- logger.error({
- message: "Last primary center extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedLastItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(
- primaryCenterList[primaryCenterList.length - 1],
- ),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "Last primary center extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_baptist_health.ts b/evals/tasks/extract_baptist_health.ts
deleted file mode 100644
index 1bb90d83e..000000000
--- a/evals/tasks/extract_baptist_health.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { compareStrings } from "@/evals/utils";
-import { z } from "zod";
-
-export const extract_baptist_health: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/baptist-health/",
- );
-
- const result = await stagehand.page.extract({
- instruction:
- "Extract the address, phone number, and fax number of the healthcare location.",
- schema: z.object({
- address: z.string(),
- phone: z.string(),
- fax: z.string(),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const { address, phone, fax } = result;
- const expected = {
- address: "2055 East South Blvd; Suite 908 Montgomery, AL 36116",
- phone: "334-747-2273",
- fax: "334-747-7501",
- };
-
- const similarityThreshold = 0.85;
- const failedFields: Array<{
- field: string;
- similarity: number;
- expected: string;
- actual: string;
- }> = [];
-
- const compareField = (
- actualVal: string,
- expectedVal: string,
- fieldName: string,
- ) => {
- const { similarity, meetsThreshold } = compareStrings(
- actualVal,
- expectedVal,
- similarityThreshold,
- );
-
- if (!meetsThreshold) {
- failedFields.push({
- field: fieldName,
- similarity,
- expected: expectedVal,
- actual: actualVal,
- });
- logger.error({
- message: `${fieldName} extracted does not meet similarity threshold`,
- level: 0,
- auxiliary: {
- field: { value: fieldName, type: "string" },
- similarity: { value: similarity.toFixed(2), type: "string" },
- expected: { value: expectedVal, type: "string" },
- actual: { value: actualVal, type: "string" },
- },
- });
- }
-
- return meetsThreshold;
- };
-
- const addressOk = compareField(address, expected.address, "Address");
- const phoneOk = compareField(phone, expected.phone, "Phone number");
- const faxOk = compareField(fax, expected.fax, "Fax number");
-
- if (!addressOk || !phoneOk || !faxOk) {
- return {
- _success: false,
- error: "Some fields did not meet similarity threshold",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- failedFields,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_capacitor_info.ts b/evals/tasks/extract_capacitor_info.ts
deleted file mode 100644
index f416b6a75..000000000
--- a/evals/tasks/extract_capacitor_info.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { normalizeString } from "@/evals/utils";
-import { z } from "zod";
-
-export const extract_capacitor_info: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/capacitor/",
- );
-
- const result = await stagehand.page.extract({
- instruction: "Extract the ECCN Code, RoHS Status, and Impedance.",
- schema: z.object({
- ECCN_code: z.string(),
- RoHS_Status: z.string(),
- Impedance: z.string(),
- }),
- useTextExtract,
- });
-
- const { ECCN_code, RoHS_Status, Impedance } = result;
-
- const expected = {
- ECCN_code: "EAR99",
- RoHS_Status: "RoHS Compliant",
- Impedance: "12mOhm",
- };
-
- if (normalizeString(ECCN_code) !== normalizeString(expected.ECCN_code)) {
- logger.error({
- message: "ECCN code extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.ECCN_code),
- type: "string",
- },
- actual: {
- value: normalizeString(ECCN_code),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "ECCN code extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- if (normalizeString(RoHS_Status) !== normalizeString(expected.RoHS_Status)) {
- logger.error({
- message: "RoHS Status extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.RoHS_Status),
- type: "string",
- },
- actual: {
- value: normalizeString(RoHS_Status),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "RoHS Status extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- if (normalizeString(Impedance) !== normalizeString(expected.Impedance)) {
- logger.error({
- message: "Impedance extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.Impedance),
- type: "string",
- },
- actual: {
- value: normalizeString(Impedance),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Impedance extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_csa.ts b/evals/tasks/extract_csa.ts
deleted file mode 100644
index 9609d1d5d..000000000
--- a/evals/tasks/extract_csa.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const extract_csa: EvalFunction = async ({
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- const page = stagehand.page;
- await page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/csa/",
- );
-
- const result = await page.extract({
- instruction:
- "Extract all the publications on the page including the publication date, session type, publication type, and annotation",
- schema: z.object({
- publications: z.array(
- z.object({
- publication_date: z.string(),
- session_type: z.string(),
- publication_type: z.string(),
- annotation: z.string(),
- }),
- ),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const publications = result.publications;
- const expectedLength = 14;
-
- const expectedFirstItem = {
- publication_date: "11-30-2024",
- session_type: "Regular Session",
- publication_type: "Assembly Weekly History",
- annotation:
- "2024 -- This publication includes the complete histories of second-year bills. The complete electronic history of all bills is always available at leginfo.legislature.ca.gov",
- };
-
- const expectedLastItem = {
- publication_date: "11-30-2016",
- session_type: "1st Extraordinary Session",
- publication_type: "Assembly Weekly History",
- annotation: "",
- };
-
- if (publications.length < expectedLength) {
- logger.error({
- message: "Incorrect number of publications extracted",
- level: 0,
- auxiliary: {
- expected: {
- value: `>= ${expectedLength}`,
- type: "integer",
- },
- actual: {
- value: publications.length.toString(),
- type: "integer",
- },
- },
- });
- return {
- _success: false,
- error: "Incorrect number of publications extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const hasExpectedFirstItem = publications.some((publication) => {
- return (
- publication.publication_date === expectedFirstItem.publication_date &&
- publication.session_type === expectedFirstItem.session_type &&
- publication.publication_type === expectedFirstItem.publication_type &&
- publication.annotation === expectedFirstItem.annotation
- );
- });
-
- if (!hasExpectedFirstItem) {
- logger.error({
- message: "Expected 'first' item not found in publications",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedFirstItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(publications),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "Expected 'first' item not found in publications",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const hasExpectedLastItem = publications.some((publication) => {
- return (
- publication.publication_date === expectedLastItem.publication_date &&
- publication.session_type === expectedLastItem.session_type &&
- publication.publication_type === expectedLastItem.publication_type &&
- publication.annotation === expectedLastItem.annotation
- );
- });
-
- if (!hasExpectedLastItem) {
- logger.error({
- message: "Expected 'last' item not found in publications",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedLastItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(publications),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "Expected 'last' item not found in publications",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_geniusee.ts b/evals/tasks/extract_geniusee.ts
deleted file mode 100644
index 18664ad6d..000000000
--- a/evals/tasks/extract_geniusee.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { z } from "zod";
-import { EvalFunction } from "@/types/evals";
-
-export const extract_geniusee: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/geniusee/",
- );
- const selector = "/html/body/main/div[2]/div[2]/div[2]/table";
- const scalability = await stagehand.page.extract({
- instruction:
- "Extract the scalability comment in the table for Gemini (Google)",
- schema: z.object({
- scalability: z.string(),
- }),
- useTextExtract,
- selector: selector,
- });
-
- await stagehand.close();
- const scalabilityComment = scalability.scalability;
-
- const expectedScalabilityComment = {
- scalability: "Scalable architecture with API access",
- };
-
- const commentMatches =
- scalabilityComment == expectedScalabilityComment.scalability;
-
- if (!commentMatches) {
- logger.error({
- message: "extracted scalability comment does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedScalabilityComment.scalability,
- type: "string",
- },
- actual: {
- value: scalabilityComment,
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "extracted scalability comment does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_geniusee_2.ts b/evals/tasks/extract_geniusee_2.ts
deleted file mode 100644
index 4a3f8006b..000000000
--- a/evals/tasks/extract_geniusee_2.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { z } from "zod";
-import { EvalFunction } from "@/types/evals";
-
-export const extract_geniusee_2: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/geniusee/",
- );
- const selector = "/html/body/main/div[2]/div[2]/div[2]/table/tbody/tr[9]";
- const scalability = await stagehand.page.extract({
- instruction:
- "Extract the scalability comment in the table for Gemini (Google)",
- schema: z.object({
- scalability: z.string(),
- }),
- useTextExtract,
- selector: selector,
- });
-
- await stagehand.close();
- const scalabilityComment = scalability.scalability;
-
- // scalabilityCommentWeShouldNotGet matches a scalability comment in the table,
- // but since we are using targeted_extract here,
- // and passing in a selector that does NOT contain the scalabilityCommentWeShouldNotGet,
- // the LLM should have no visibility into scalabilityCommentWeShouldNotGet if
- // targeted_extract is performing correctly
- const scalabilityCommentWeShouldNotGet = {
- scalability: "Scalable architecture with API access",
- };
-
- const commentMatches =
- scalabilityComment == scalabilityCommentWeShouldNotGet.scalability;
-
- if (commentMatches) {
- logger.error({
- message:
- "extracted scalability comment matches the scalability comment that we SHOULD NOT get",
- level: 0,
- auxiliary: {
- expected: {
- value: scalabilityCommentWeShouldNotGet.scalability,
- type: "string",
- },
- actual: {
- value: scalabilityComment,
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error:
- "scalability comment matches the scalability comment that we SHOULD NOT get",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_jstor_news.ts b/evals/tasks/extract_jstor_news.ts
deleted file mode 100644
index 4c5c690c9..000000000
--- a/evals/tasks/extract_jstor_news.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const extract_jstor_news: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/jstor/",
- {
- waitUntil: "load",
- },
- );
- await stagehand.page.act({ action: "close the cookie" });
-
- const result = await stagehand.page.extract({
- instruction: "Extract ALL the news report titles and their dates.",
- schema: z.object({
- reports: z.array(
- z.object({
- report_name: z
- .string()
- .describe("The name or title of the news report."),
- publish_date: z
- .string()
- .describe("The date the news report was published."),
- }),
- ),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const reports = result.reports;
- const expectedLength = 10;
-
- const expectedFirstItem = {
- report_name: "JSTOR retires Publisher Sales Service",
- publish_date: "December 9, 2024",
- };
-
- const expectedLastItem = {
- report_name: "Path to Open announces 2024 titles",
- publish_date: "May 10, 2024",
- };
-
- if (reports.length !== expectedLength) {
- logger.error({
- message: "Incorrect number of reports extracted",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedLength.toString(),
- type: "integer",
- },
- actual: {
- value: reports.length.toString(),
- type: "integer",
- },
- },
- });
- return {
- _success: false,
- error: "Incorrect number of reports extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
- const firstItemMatches =
- reports[0].report_name === expectedFirstItem.report_name &&
- reports[0].publish_date === expectedFirstItem.publish_date;
-
- if (!firstItemMatches) {
- logger.error({
- message: "First report extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedFirstItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(reports[0]),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "First report extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const lastItemMatches =
- reports[reports.length - 1].report_name === expectedLastItem.report_name &&
- reports[reports.length - 1].publish_date === expectedLastItem.publish_date;
-
- if (!lastItemMatches) {
- logger.error({
- message: "Last report extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedLastItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(reports[reports.length - 1]),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "Last report extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_memorial_healthcare.ts b/evals/tasks/extract_memorial_healthcare.ts
deleted file mode 100644
index c01dd5480..000000000
--- a/evals/tasks/extract_memorial_healthcare.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-import { compareStrings } from "@/evals/utils";
-
-export const extract_memorial_healthcare: EvalFunction = async ({
- logger,
- useTextExtract,
- debugUrl,
- sessionUrl,
- stagehand,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/mycmh/",
- );
-
- const result = await stagehand.page.extract({
- instruction:
- "extract a list of the first three healthcare centers on this page, with their name, full address, and phone number",
- schema: z.object({
- health_centers: z.array(
- z.object({
- name: z.string(),
- phone_number: z.string(),
- address: z.string(),
- }),
- ),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const health_centers: Array<
- Partial<{ name: string; phone_number: string; address: string }>
- > = result.health_centers;
-
- const expectedLength = 3;
- const similarityThreshold = 0.85;
-
- const expectedFirstItem = {
- name: "Community Memorial Breast Center",
- phone_number: "805-948-5093",
- address: "168 North Brent Street, Suite 401, Ventura, CA 93003",
- };
-
- const expectedLastItem = {
- name: "Community Memorial Dermatology and Mohs Surgery",
- phone_number: "805-948-6920",
- address: "168 North Brent Street, Suite 403, Ventura, CA 93003",
- };
-
- if (health_centers.length !== expectedLength) {
- logger.error({
- message: "Incorrect number of health centers extracted",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedLength.toString(),
- type: "integer",
- },
- actual: {
- value: health_centers.length.toString(),
- type: "integer",
- },
- },
- });
-
- return {
- _success: false,
- error: "Incorrect number of health centers extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const validateHealthCenter = (
- center: Partial<{ name: string; phone_number: string; address: string }>,
- ): { name: string; phone_number: string; address: string } | null => {
- if (center.name && center.phone_number && center.address) {
- return center as { name: string; phone_number: string; address: string };
- }
- logger.error({
- message: "Invalid health center data",
- level: 0,
- auxiliary: {
- center: { value: JSON.stringify(center), type: "object" },
- },
- });
- return null;
- };
-
- const validHealthCenters = health_centers
- .map(validateHealthCenter)
- .filter(Boolean) as Array<{
- name: string;
- phone_number: string;
- address: string;
- }>;
-
- if (validHealthCenters.length < expectedLength) {
- return {
- _success: false,
- error: "One or more health centers have missing fields",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const compareField = (
- actual: string,
- expected: string,
- fieldName: string,
- ): boolean => {
- const { similarity, meetsThreshold } = compareStrings(
- actual,
- expected,
- similarityThreshold,
- );
-
- if (!meetsThreshold) {
- logger.error({
- message: `Field "${fieldName}" does not meet similarity threshold`,
- level: 0,
- auxiliary: {
- field: { value: fieldName, type: "string" },
- similarity: { value: similarity.toFixed(2), type: "float" },
- expected: { value: expected, type: "string" },
- actual: { value: actual, type: "string" },
- },
- });
- }
-
- return meetsThreshold;
- };
-
- const compareItem = (
- actual: { name: string; phone_number: string; address: string },
- expected: { name: string; phone_number: string; address: string },
- position: string,
- ): boolean => {
- const fields = [
- { field: "name", actual: actual.name, expected: expected.name },
- {
- field: "phone_number",
- actual: actual.phone_number,
- expected: expected.phone_number,
- },
- { field: "address", actual: actual.address, expected: expected.address },
- ];
-
- return fields.every(({ field, actual, expected }) =>
- compareField(actual, expected, `${position} ${field}`),
- );
- };
-
- const firstItemMatches = compareItem(
- validHealthCenters[0],
- expectedFirstItem,
- "First",
- );
- const lastItemMatches = compareItem(
- validHealthCenters[validHealthCenters.length - 1],
- expectedLastItem,
- "Last",
- );
-
- if (!firstItemMatches || !lastItemMatches) {
- return {
- _success: false,
- error: "One or more fields do not match expected values",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_nhl_stats.ts b/evals/tasks/extract_nhl_stats.ts
deleted file mode 100644
index fb9e982ac..000000000
--- a/evals/tasks/extract_nhl_stats.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { normalizeString } from "@/evals/utils";
-import { z } from "zod";
-
-export const extract_nhl_stats: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://www.hockeydb.com/ihdb/stats/top_league.php?lid=nhl1927&sid=1990",
- {
- waitUntil: "domcontentloaded",
- },
- );
-
- const result = await stagehand.page.extract({
- instruction:
- "Extract the name of the goal scoring leader, their number of goals they scored, and the team they played for.",
- schema: z.object({
- name: z.string(),
- num_goals: z.string(),
- team: z.string(),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const { name, num_goals, team } = result;
-
- const expected = {
- name: "Brett Hull",
- num_goals: "72",
- team: "St. Louis",
- };
-
- if (normalizeString(name) !== normalizeString(expected.name)) {
- logger.error({
- message: "Player name extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.name),
- type: "string",
- },
- actual: {
- value: normalizeString(name),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Player name extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- if (normalizeString(num_goals) !== normalizeString(expected.num_goals)) {
- logger.error({
- message: "Number of goals extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.num_goals),
- type: "string",
- },
- actual: {
- value: normalizeString(num_goals),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Number of goals extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- if (normalizeString(team) !== normalizeString(expected.team)) {
- logger.error({
- message: "Player team extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.team),
- type: "string",
- },
- actual: {
- value: normalizeString(team),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Player team extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_professional_info.ts b/evals/tasks/extract_professional_info.ts
deleted file mode 100644
index c77708f5c..000000000
--- a/evals/tasks/extract_professional_info.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { normalizeString } from "@/evals/utils";
-import { z } from "zod";
-
-export const extract_professional_info: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/professional-info/",
- );
-
- const result = await stagehand.page.extract({
- instruction:
- "Extract the list of Practices, phone number, and fax number of the professional.",
- schema: z.object({
- practices: z.array(z.string()),
- phone: z.string(),
- fax: z.string(),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const { practices, phone, fax } = result;
-
- const expected = {
- practices: [
- "Restructuring",
- "Finance",
- "Hybrid Capital & Special Situations",
- "Private Credit",
- ],
- phone: "+1-212-373-3262",
- fax: "+1-212-492-0262",
- };
-
- if (
- JSON.stringify(practices.map(normalizeString)) !==
- JSON.stringify(expected.practices.map(normalizeString))
- ) {
- logger.error({
- message: "Practices extracted do not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expected.practices),
- type: "object",
- },
- actual: {
- value: JSON.stringify(practices),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "Practices extracted do not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- if (normalizeString(phone) !== normalizeString(expected.phone)) {
- logger.error({
- message: "Phone number extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.phone),
- type: "string",
- },
- actual: {
- value: normalizeString(phone),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Phone number extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- if (normalizeString(fax) !== normalizeString(expected.fax)) {
- logger.error({
- message: "Fax number extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.fax),
- type: "string",
- },
- actual: {
- value: normalizeString(fax),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Fax number extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_public_notices.ts b/evals/tasks/extract_public_notices.ts
deleted file mode 100644
index 73a76864c..000000000
--- a/evals/tasks/extract_public_notices.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-import { compareStrings } from "@/evals/utils";
-
-export const extract_public_notices: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/sars/",
- { waitUntil: "load" },
- );
-
- const result = await stagehand.page.extract({
- instruction:
- "Extract ALL the public notice descriptions with their corresponding, GG number and publication date. Extract ALL notices from 2024 through 2020. Do not include the Notice number.",
- schema: z.object({
- public_notices: z.array(
- z.object({
- notice_description: z
- .string()
- .describe(
- "the description of the notice. Do not include the Notice number",
- ),
- gg_number: z
- .string()
- .describe("the GG number of the notice. For example, GG 12345"),
- publication_date: z
- .string()
- .describe(
- "the publication date of the notice. For example, 8 December 2021",
- ),
- }),
- ),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const publicNotices = result.public_notices;
- const expectedLength = 24;
-
- const expectedFirstItem = {
- notice_description:
- "Additional considerations in terms of section 80(2) in respect of which an application for a binding private ruling or a binding class ruling may be rejected",
- gg_number: "GG 51526",
- publication_date: "8 November 2024",
- };
-
- const expectedLastItem = {
- notice_description:
- "Notice in terms of section 25, read with section 66(1) of the Income Tax Act, 1962, for submission of 2020 income tax returns",
- gg_number: "GG 43495",
- publication_date: "3 July 2020",
- };
-
- if (publicNotices.length !== expectedLength) {
- logger.error({
- message: "Incorrect number of public notices extracted",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedLength.toString(),
- type: "integer",
- },
- actual: {
- value: publicNotices.length.toString(),
- type: "integer",
- },
- },
- });
- return {
- _success: false,
- error: "Incorrect number of public notices extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
- const firstItemMatches =
- compareStrings(
- publicNotices[0].notice_description,
- expectedFirstItem.notice_description,
- 0.9,
- ) &&
- compareStrings(
- publicNotices[0].gg_number,
- expectedFirstItem.gg_number,
- 0.9,
- ) &&
- compareStrings(
- publicNotices[0].publication_date,
- expectedFirstItem.publication_date,
- 0.9,
- );
-
- if (!firstItemMatches) {
- logger.error({
- message: "First public notice extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedFirstItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(publicNotices[0]),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "First public notice extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const lastItemMatches =
- compareStrings(
- publicNotices[publicNotices.length - 1].notice_description,
- expectedLastItem.notice_description,
- 0.9,
- ) &&
- compareStrings(
- publicNotices[publicNotices.length - 1].gg_number,
- expectedLastItem.gg_number,
- 0.9,
- ) &&
- compareStrings(
- publicNotices[publicNotices.length - 1].publication_date,
- expectedLastItem.publication_date,
- 0.9,
- );
-
- if (!lastItemMatches) {
- logger.error({
- message: "Last public notice extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedLastItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(publicNotices[publicNotices.length - 1]),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: "Last public notice extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_recipe.ts b/evals/tasks/extract_recipe.ts
deleted file mode 100644
index 1e5040688..000000000
--- a/evals/tasks/extract_recipe.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const extract_recipe: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/allrecipes-extract/",
- {
- waitUntil: "domcontentloaded",
- },
- );
-
- const selector = "/html/body/main/article/div[3]/div[3]/div[4]";
- const recipeDetails = await stagehand.page.extract({
- instruction:
- "Extract the title of the number of tablespoons of olive oil needed for the steak, and the number of teaspoons of lemon juice needed for the mushroom pan sauce.",
- schema: z.object({
- tablespoons_olive_oil: z
- .number()
- .describe(
- "the number of tablespoons of olive oil needed for the steak",
- ),
- teaspoons_lemon_juice: z
- .number()
- .describe(
- "the number of teaspoons of lemon juice needed for the mushroom pan sauce",
- ),
- }),
- useTextExtract,
- selector: selector,
- });
-
- await stagehand.close();
-
- const { tablespoons_olive_oil, teaspoons_lemon_juice } = recipeDetails;
- const expectedTablespoons = 2;
- const expectedTeaspoons = 2;
-
- if (
- tablespoons_olive_oil !== expectedTablespoons ||
- teaspoons_lemon_juice !== expectedTeaspoons
- ) {
- const errors = [];
- if (tablespoons_olive_oil !== expectedTablespoons) {
- errors.push({
- message:
- "Extracted tablespoons of olive oil do not match the extracted tablespoons of olive oil",
- expected: expectedTablespoons.toString(),
- actual: tablespoons_olive_oil.toString(),
- });
- }
- if (teaspoons_lemon_juice !== expectedTeaspoons) {
- errors.push({
- message:
- "Extracted teaspoons of lemon juice do not match the extracted teaspoons of lemon juice",
- expected: expectedTeaspoons.toString(),
- actual: teaspoons_lemon_juice.toString(),
- });
- }
-
- logger.error({
- message: "Failed to extract correct recipe details",
- level: 0,
- auxiliary: {
- errors: {
- value: JSON.stringify(errors),
- type: "object",
- },
- },
- });
-
- return {
- _success: false,
- error: "Recipe details extraction validation failed",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- recipeDetails: {
- tablespoons_olive_oil: expectedTablespoons,
- teaspoons_lemon_juice: expectedTeaspoons,
- },
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_resistor_info.ts b/evals/tasks/extract_resistor_info.ts
deleted file mode 100644
index 4f4f0b265..000000000
--- a/evals/tasks/extract_resistor_info.ts
+++ /dev/null
@@ -1,157 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { normalizeString } from "@/evals/utils";
-import { z } from "zod";
-
-export const extract_resistor_info: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/resistor/",
- );
-
- const result = await stagehand.page.extract({
- instruction:
- "Extract the manufacturer standard lead time, tolerance percentage, resistance, and operating temperature range of the resistor.",
- schema: z.object({
- manufacturer_standard_lead_time: z.string(),
- tolerance_percentage: z.string(),
- resistance: z.string(),
- operating_temperature_range: z.string(),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const {
- manufacturer_standard_lead_time,
- tolerance_percentage,
- resistance,
- operating_temperature_range,
- } = result;
-
- const expected = {
- manufacturer_standard_lead_time: "11 Weeks",
- tolerance_percentage: "±5",
- resistance: "330 ohms",
- operating_temperature_range: "-55°C ~ 155°C",
- };
-
- if (
- normalizeString(manufacturer_standard_lead_time) !==
- normalizeString(expected.manufacturer_standard_lead_time)
- ) {
- logger.error({
- message:
- "manufacturer standard lead time extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.manufacturer_standard_lead_time),
- type: "string",
- },
- actual: {
- value: normalizeString(manufacturer_standard_lead_time),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error:
- "manufacturer standard lead time extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- if (
- normalizeString(tolerance_percentage) !==
- normalizeString(expected.tolerance_percentage)
- ) {
- logger.error({
- message: "Tolerance percentage extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.tolerance_percentage),
- type: "string",
- },
- actual: {
- value: normalizeString(tolerance_percentage),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Tolerance percentage extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- if (normalizeString(resistance) !== normalizeString(expected.resistance)) {
- logger.error({
- message: "resistance extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.resistance),
- type: "string",
- },
- actual: {
- value: normalizeString(resistance),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "resistance extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- if (
- normalizeString(operating_temperature_range) !==
- normalizeString(expected.operating_temperature_range)
- ) {
- logger.error({
- message: "Operating temperature range extracted does not match expected",
- level: 0,
- auxiliary: {
- expected: {
- value: normalizeString(expected.operating_temperature_range),
- type: "string",
- },
- actual: {
- value: normalizeString(operating_temperature_range),
- type: "string",
- },
- },
- });
- return {
- _success: false,
- error: "Operating temperature range extracted does not match expected",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_rockauto.ts b/evals/tasks/extract_rockauto.ts
deleted file mode 100644
index f154790de..000000000
--- a/evals/tasks/extract_rockauto.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const extract_rockauto: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/rockauto/",
- );
- await new Promise((resolve) => setTimeout(resolve, 5000));
- const result = await stagehand.page.extract({
- instruction:
- "Extract the part number of all the coolant and antifreeze products in the 'economy' category. " +
- "Do not include the manufacturer name. Do not include products from the premium category.",
- schema: z.object({
- coolant_products: z.array(
- z.object({
- part_number: z.string(),
- }),
- ),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const coolantProducts = result.coolant_products;
- const expectedPartNumbers = [
- "GREEN5050GAL",
- "719009",
- "AF3300",
- "AF3100",
- "MV5050GAL",
- ];
- const expectedLength = expectedPartNumbers.length;
-
- if (coolantProducts.length !== expectedLength) {
- logger.error({
- message: "Incorrect number of coolant products extracted",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedLength.toString(),
- type: "integer",
- },
- actual: {
- value: coolantProducts.length.toString(),
- type: "integer",
- },
- },
- });
- return {
- _success: false,
- error: "Incorrect number of coolant products extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const missingParts = expectedPartNumbers.filter(
- (expectedPart) =>
- !coolantProducts.some((p) => p.part_number === expectedPart),
- );
-
- if (missingParts.length > 0) {
- logger.error({
- message: "Missing expected part number(s)",
- level: 0,
- auxiliary: {
- missingParts: {
- value: JSON.stringify(missingParts),
- type: "object",
- },
- actualExtracted: {
- value: JSON.stringify(coolantProducts),
- type: "object",
- },
- },
- });
- return {
- _success: false,
- error: `One or more expected part numbers were not found: ${missingParts.join(", ")}`,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_staff_members.ts b/evals/tasks/extract_staff_members.ts
deleted file mode 100644
index 7cf396773..000000000
--- a/evals/tasks/extract_staff_members.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import { z } from "zod";
-import { EvalFunction } from "@/types/evals";
-
-export const extract_staff_members: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/panamcs/",
- );
-
- const result = await stagehand.page.extract({
- instruction:
- "extract a list of ALL the staff members on this page, with their name and their job title",
- schema: z.object({
- staff_members: z.array(
- z.object({
- name: z.string(),
- job_title: z.string(),
- }),
- ),
- }),
- useTextExtract,
- });
-
- const staff_members = result.staff_members;
- await stagehand.close();
-
- const expectedLength = 50;
-
- const expectedFirstItem = {
- name: "Louis Alvarez",
- job_title: "School Resource Officer",
- };
-
- const expectedLastItem = {
- name: "Jessica Zipin",
- job_title: "School Based Therapist",
- };
-
- if (staff_members.length !== expectedLength) {
- logger.error({
- message: "Incorrect number of items extracted",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedLength.toString(),
- type: "integer",
- },
- actual: {
- value: staff_members.length.toString(),
- type: "integer",
- },
- },
- });
-
- return {
- _success: false,
- error: "Incorrect number of staff members extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- // Check for the presence of the expected items
- const firstItemExists = staff_members.some(
- (member) =>
- member.name === expectedFirstItem.name &&
- member.job_title === expectedFirstItem.job_title,
- );
-
- if (!firstItemExists) {
- logger.error({
- message: "Expected first staff member not found in extracted data",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedFirstItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(staff_members),
- type: "object",
- },
- },
- });
-
- await stagehand.close();
-
- return {
- _success: false,
- error: "Expected first staff member not found in extracted data",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const lastItemExists = staff_members.some(
- (member) =>
- member.name === expectedLastItem.name &&
- member.job_title === expectedLastItem.job_title,
- );
-
- if (!lastItemExists) {
- logger.error({
- message: "Expected last staff member not found in extracted data",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedLastItem),
- type: "object",
- },
- actual: {
- value: JSON.stringify(staff_members),
- type: "object",
- },
- },
- });
-
- await stagehand.close();
-
- return {
- _success: false,
- error: "Expected last staff member not found in extracted data",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- await stagehand.close();
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/extract_zillow.ts b/evals/tasks/extract_zillow.ts
deleted file mode 100644
index 1902f281f..000000000
--- a/evals/tasks/extract_zillow.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { z } from "zod";
-import { EvalFunction } from "../../types/evals";
-
-export const extract_zillow: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/zillow/",
- );
- // timeout for 5 seconds
- await stagehand.page.waitForTimeout(5000);
- const real_estate_listings = await stagehand.page.extract({
- instruction:
- "Extract EACH AND EVERY HOME PRICE AND ADDRESS ON THE PAGE. DO NOT MISS ANY OF THEM.",
- schema: z.object({
- listings: z.array(
- z.object({
- price: z.string().describe("The price of the home"),
- trails: z.string().describe("The address of the home"),
- }),
- ),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
- const listings = real_estate_listings.listings;
- const expectedLength = 38;
-
- if (listings.length < expectedLength) {
- logger.error({
- message: "Incorrect number of listings extracted",
- level: 0,
- auxiliary: {
- expected: {
- value: expectedLength.toString(),
- type: "integer",
- },
- actual: {
- value: listings.length.toString(),
- type: "integer",
- },
- },
- });
- return {
- _success: false,
- error: "Incorrect number of listings extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/google_flights.ts b/evals/tasks/google_flights.ts
deleted file mode 100644
index 73654dc1e..000000000
--- a/evals/tasks/google_flights.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { ObserveResult } from "@/types/stagehand";
-
-/**
- * This eval attempts to click on an element that should not pass the playwright actionability check
- * which happens by default if you call locator.click (more information here:
- * https://playwright.dev/docs/actionability)
- *
- * If this eval passes, it means that we have correctly set {force: true} in performPlaywrightMethod,
- * and the click was successful even though the target element (found by the xpath) did not
- * pass the actionability check.
- */
-
-export const google_flights: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/google-flights/",
- );
-
- const observeResult: ObserveResult = {
- selector:
- "xpath=/html/body/c-wiz[2]/div/div[2]/c-wiz/div[1]/c-wiz/div[2]/div[2]/div[2]/div/div[2]/div[1]/ul/li[1]/div/div[1]",
- description: "the first departing flight",
- method: "click",
- arguments: [],
- };
- await stagehand.page.act(observeResult);
-
- const expectedUrl =
- "https://browserbase.github.io/stagehand-eval-sites/sites/google-flights/return-flight.html";
- const currentUrl = stagehand.page.url();
-
- await stagehand.close();
-
- if (currentUrl === expectedUrl) {
- return {
- _success: true,
- currentUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
- return {
- _success: false,
- error: "The current URL does not match expected.",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/google_jobs.ts b/evals/tasks/google_jobs.ts
deleted file mode 100644
index a8e139988..000000000
--- a/evals/tasks/google_jobs.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const google_jobs: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- try {
- await stagehand.page.goto("https://www.google.com/");
- await stagehand.page.act("click on the about page");
- await stagehand.page.act("click on the careers page");
- await stagehand.page.act("input data scientist into role");
- await stagehand.page.act("input new york city into location");
- await stagehand.page.act("click on the search button");
- await stagehand.page.act("click on the first job link");
-
- const jobDetails = await stagehand.page.extract({
- instruction:
- "Extract the following details from the job posting: application deadline, minimum qualifications (degree and years of experience), and preferred qualifications (degree and years of experience)",
- schema: z.object({
- applicationDeadline: z
- .string()
- .describe("The date until which the application window will be open")
- .nullable(),
- minimumQualifications: z.object({
- degree: z.string().describe("The minimum required degree").nullable(),
- yearsOfExperience: z
- .number()
- .describe("The minimum required years of experience")
- .nullable(),
- }),
- preferredQualifications: z.object({
- degree: z.string().describe("The preferred degree").nullable(),
- yearsOfExperience: z
- .number()
- .describe("The preferred years of experience")
- .nullable(),
- }),
- }),
- useTextExtract,
- });
-
- const isJobDetailsValid =
- jobDetails &&
- Object.values(jobDetails).every(
- (value) =>
- value !== null &&
- value !== undefined &&
- (typeof value !== "object" ||
- Object.values(value).every(
- (v) =>
- v !== null &&
- v !== undefined &&
- (typeof v === "number" || typeof v === "string"),
- )),
- );
-
- await stagehand.close();
-
- return {
- _success: isJobDetailsValid,
- jobDetails,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- } catch (error) {
- logger.error({
- message: "error in google_jobs function",
- level: 0,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
-
- await stagehand.close();
-
- return {
- _success: false,
- debugUrl,
- sessionUrl,
- error: JSON.parse(JSON.stringify(error, null, 2)),
- logs: logger.getLogs(),
- };
- }
-};
diff --git a/evals/tasks/history.ts b/evals/tasks/history.ts
deleted file mode 100644
index a41eb256b..000000000
--- a/evals/tasks/history.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const history: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://docs.stagehand.dev");
-
- await stagehand.page.act("click on the 'Quickstart' tab");
-
- await stagehand.page.extract("Extract the title of the page");
-
- await stagehand.page.observe("Find all links on the page");
-
- const history = stagehand.history;
-
- const hasCorrectNumberOfEntries = history.length === 4;
-
- const hasNavigateEntry = history[0].method === "navigate";
- const hasActEntry = history[1].method === "act";
- const hasExtractEntry = history[2].method === "extract";
- const hasObserveEntry = history[3].method === "observe";
-
- const allEntriesHaveTimestamps = history.every(
- (entry) =>
- typeof entry.timestamp === "string" && entry.timestamp.length > 0,
- );
- const allEntriesHaveResults = history.every(
- (entry) => entry.result !== undefined,
- );
-
- await stagehand.close();
-
- const success =
- hasCorrectNumberOfEntries &&
- hasNavigateEntry &&
- hasActEntry &&
- hasExtractEntry &&
- hasObserveEntry &&
- allEntriesHaveTimestamps &&
- allEntriesHaveResults;
-
- return {
- _success: success,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/homedepot.ts b/evals/tasks/homedepot.ts
deleted file mode 100644
index ca4b39b85..000000000
--- a/evals/tasks/homedepot.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const homedepot: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- try {
- await stagehand.page.goto("https://www.homedepot.com/");
- await stagehand.page.act("search for gas grills");
- await stagehand.page.act("click on the best selling gas grill");
- await stagehand.page.act("click on the Product Details");
- await stagehand.page.act("find the Primary Burner BTU");
-
- const productSpecs = await stagehand.page.extract({
- instruction: "Extract the Primary exact Burner BTU of the product",
- schema: z.object({
- productSpecs: z
- .array(
- z.object({
- burnerBTU: z.string().describe("Primary Burner BTU exact value"),
- }),
- )
- .describe("Gas grill Primary Burner BTU exact value"),
- }),
- useTextExtract,
- });
-
- logger.log({
- message: `gas grill primary burner BTU`,
- level: 1,
- auxiliary: {
- productSpecs: {
- value: JSON.stringify(productSpecs),
- type: "object",
- },
- },
- });
-
- if (
- !productSpecs ||
- !productSpecs.productSpecs ||
- productSpecs.productSpecs.length !== 1
- ) {
- await stagehand.close();
-
- return {
- _success: false,
- productSpecs,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- const hasFourZerosAndOne4 =
- (productSpecs.productSpecs[0].burnerBTU.match(/0/g) || []).length === 4 &&
- (productSpecs.productSpecs[0].burnerBTU.match(/4/g) || []).length === 1;
-
- await stagehand.close();
-
- return {
- _success: hasFourZerosAndOne4,
- productSpecs,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- } catch (error) {
- logger.error({
- message: "error in homedepot function",
- level: 0,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
-
- await stagehand.close();
-
- return {
- _success: false,
- error: JSON.parse(JSON.stringify(error, null, 2)),
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-};
diff --git a/evals/tasks/imdb_movie_details.ts b/evals/tasks/imdb_movie_details.ts
deleted file mode 100644
index 6e45bd2a0..000000000
--- a/evals/tasks/imdb_movie_details.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const imdb_movie_details: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto("https://www.imdb.com/title/tt0111161/", {
- waitUntil: "domcontentloaded",
- });
- await stagehand.page.act({
- action: "click on the movie ratings",
- });
-
- const movieDetails = await stagehand.page.extract({
- instruction: "Extract the list of countries with the most ratings.",
- schema: z.object({
- countries: z
- .array(z.string())
- .describe("List of countries with the most ratings"),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const expectedCountries = [
- "United States",
- "United Kingdom",
- "Turkey",
- "India",
- "Germany",
- ];
-
- if (!movieDetails.countries || movieDetails.countries.length !== 5) {
- logger.error({
- message: "Failed to extract exactly five countries",
- level: 0,
- auxiliary: {
- expected: {
- value: JSON.stringify(expectedCountries),
- type: "object",
- },
- actual: {
- value: JSON.stringify(movieDetails.countries || []),
- type: "object",
- },
- },
- });
-
- return {
- _success: false,
- error: "Incorrect number of countries extracted",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const missingCountries = expectedCountries.filter(
- (country) => !movieDetails.countries.includes(country),
- );
-
- if (missingCountries.length > 0) {
- logger.error({
- message: "Extracted countries do not match expected countries",
- level: 0,
- auxiliary: {
- missing: {
- value: JSON.stringify(missingCountries),
- type: "object",
- },
- extracted: {
- value: JSON.stringify(movieDetails.countries),
- type: "object",
- },
- },
- });
-
- return {
- _success: false,
- error: "Extracted countries do not match expected countries",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- countries: movieDetails.countries,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/ionwave.ts b/evals/tasks/ionwave.ts
deleted file mode 100644
index e4d4e200d..000000000
--- a/evals/tasks/ionwave.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const ionwave: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://elpasotexas.ionwave.net/Login.aspx");
-
- await stagehand.page.act({
- action: 'Click on "Closed Bids"',
- });
-
- const expectedUrl =
- "https://elpasotexas.ionwave.net/SourcingEvents.aspx?SourceType=2";
- const currentUrl = stagehand.page.url();
-
- await stagehand.close();
-
- return {
- _success: currentUrl.startsWith(expectedUrl),
- currentUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/ionwave_observe.ts b/evals/tasks/ionwave_observe.ts
deleted file mode 100644
index 8fa3197dc..000000000
--- a/evals/tasks/ionwave_observe.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const ionwave_observe: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://elpasotexas.ionwave.net/Login.aspx");
-
- const observations = await stagehand.page.observe({ onlyVisible: true });
-
- if (observations.length === 0) {
- await stagehand.close();
- return {
- _success: false,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- const expectedLocator = `div.rowLinks:nth-child(27) > div:nth-child(1) > a:nth-child(1)`;
-
- const expectedResult = await stagehand.page
- .locator(expectedLocator)
- .first()
- .innerText();
-
- let foundMatch = false;
- for (const observation of observations) {
- try {
- const observationResult = await stagehand.page
- .locator(observation.selector)
- .first()
- .innerText();
-
- if (observationResult === expectedResult) {
- foundMatch = true;
- break;
- }
- } catch (error) {
- console.warn(
- `Failed to check observation with selector ${observation.selector}:`,
- error.message,
- );
- continue;
- }
- }
-
- await stagehand.close();
-
- return {
- _success: foundMatch,
- expected: expectedResult,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/nextChunk.ts b/evals/tasks/nextChunk.ts
deleted file mode 100644
index 6591cb8c5..000000000
--- a/evals/tasks/nextChunk.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Stagehand } from "@/dist";
-import { EvalFunction } from "@/types/evals";
-
-export const nextChunk: EvalFunction = async ({
- logger,
- stagehandConfig,
- debugUrl,
- sessionUrl,
-}) => {
- const stagehand = new Stagehand({
- ...stagehandConfig,
- domSettleTimeoutMs: 3000,
- });
- await stagehand.init();
-
- await stagehand.page.goto("https://www.apartments.com/san-francisco-ca/");
- await stagehand.page.act({
- action: "click on the all filters button",
- });
-
- const { initialScrollTop, chunkHeight } = await stagehand.page.evaluate(
- () => {
- const container = document.querySelector(
- "#advancedFilters > div",
- ) as HTMLElement;
- if (!container) {
- console.warn(
- "Could not find #advancedFilters > div. Returning 0 for measurements.",
- );
- return { initialScrollTop: 0, chunkHeight: 0 };
- }
- return {
- initialScrollTop: container.scrollTop,
- chunkHeight: container.getBoundingClientRect().height,
- };
- },
- );
-
- await stagehand.page.act({
- action: "scroll down one chunk on the filters modal",
- });
-
- await new Promise((resolve) => setTimeout(resolve, 2000));
-
- const newScrollTop = await stagehand.page.evaluate(() => {
- const container = document.querySelector(
- "#advancedFilters > div",
- ) as HTMLElement;
- return container?.scrollTop ?? 0;
- });
-
- await stagehand.close();
-
- const actualDiff = newScrollTop - initialScrollTop;
- const threshold = 20; // allowable difference in px
- const scrolledOneChunk = Math.abs(actualDiff - chunkHeight) <= threshold;
-
- const evaluationResult = scrolledOneChunk
- ? {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- message: `Successfully scrolled ~one chunk: expected ~${chunkHeight}, got ${actualDiff}`,
- }
- : {
- _success: false,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- message: `Scroll difference expected ~${chunkHeight} but only scrolled ${actualDiff}.`,
- };
-
- return evaluationResult;
-};
diff --git a/evals/tasks/observe_amazon_add_to_cart.ts b/evals/tasks/observe_amazon_add_to_cart.ts
deleted file mode 100644
index a9b810380..000000000
--- a/evals/tasks/observe_amazon_add_to_cart.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const observe_amazon_add_to_cart: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/amazon/",
- );
-
- await stagehand.page.waitForTimeout(5000);
-
- const observations1 = await stagehand.page.observe({
- instruction: "Find and click the 'Add to Cart' button",
- onlyVisible: false,
- returnAction: true,
- });
-
- console.log(observations1);
-
- // Example of using performPlaywrightMethod if you have the xpath
- if (observations1.length > 0) {
- const action1 = observations1[0];
- await stagehand.page.act(action1);
- }
-
- await stagehand.page.waitForTimeout(2000);
-
- const observations2 = await stagehand.page.observe({
- instruction: "Find and click the 'Proceed to checkout' button",
- });
-
- // Example of using performPlaywrightMethod if you have the xpath
- if (observations2.length > 0) {
- const action2 = observations2[0];
- await stagehand.page.act(action2);
- }
- await stagehand.page.waitForTimeout(2000);
-
- const currentUrl = stagehand.page.url();
- const expectedUrlPrefix =
- "https://browserbase.github.io/stagehand-eval-sites/sites/amazon/sign-in.html";
-
- await stagehand.close();
-
- return {
- _success: currentUrl.startsWith(expectedUrlPrefix),
- currentUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/observe_github.ts b/evals/tasks/observe_github.ts
deleted file mode 100644
index 036255f85..000000000
--- a/evals/tasks/observe_github.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const observe_github: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://github.com/numpy/numpy/tree/main/numpy");
-
- const observations = await stagehand.page.observe({
- instruction: "find the scrollable element that holds the repos file tree.",
- });
-
- if (observations.length === 0) {
- await stagehand.close();
- return {
- _success: false,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- const possibleLocators = [
- `#repos-file-tree > div.Box-sc-g0xbh4-0.jbQqON > div > div > div > nav > ul`,
- `#repos-file-tree > div.Box-sc-g0xbh4-0.jbQqON > div > div > div > nav`,
- `#repos-file-tree > div.Box-sc-g0xbh4-0.jbQqON`,
- ];
-
- const possibleHandles = [];
- for (const locatorStr of possibleLocators) {
- const locator = stagehand.page.locator(locatorStr);
- const handle = await locator.elementHandle();
- if (handle) {
- possibleHandles.push({ locatorStr, handle });
- }
- }
-
- let foundMatch = false;
- let matchedLocator: string | null = null;
-
- for (const observation of observations) {
- try {
- const observationLocator = stagehand.page
- .locator(observation.selector)
- .first();
- const observationHandle = await observationLocator.elementHandle();
- if (!observationHandle) {
- continue;
- }
-
- for (const { locatorStr, handle: candidateHandle } of possibleHandles) {
- const isSameNode = await observationHandle.evaluate(
- (node, otherNode) => node === otherNode,
- candidateHandle,
- );
- if (isSameNode) {
- foundMatch = true;
- matchedLocator = locatorStr;
- break;
- }
- }
-
- if (foundMatch) {
- break;
- }
- } catch (error) {
- console.warn(
- `Failed to check observation with selector ${observation.selector}:`,
- error.message,
- );
- continue;
- }
- }
-
- await stagehand.close();
-
- return {
- _success: foundMatch,
- matchedLocator,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/observe_iframes1.ts b/evals/tasks/observe_iframes1.ts
deleted file mode 100644
index f1cbc68ee..000000000
--- a/evals/tasks/observe_iframes1.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const observe_iframes1: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://tucowsdomains.com/abuse-form/phishing/");
-
- const observations = await stagehand.page.observe({
- instruction: "find the main header of the page",
- });
-
- if (observations.length === 0) {
- await stagehand.close();
- return {
- _success: false,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- const possibleLocators = [
- `#primary > div.singlePage > section > div > div > article > div > iframe`,
- `#primary > div.heroBanner > section > div > h1`,
- ];
-
- const possibleHandles = [];
- for (const locatorStr of possibleLocators) {
- const locator = stagehand.page.locator(locatorStr);
- const handle = await locator.elementHandle();
- if (handle) {
- possibleHandles.push({ locatorStr, handle });
- }
- }
-
- let foundMatch = false;
- let matchedLocator: string | null = null;
-
- for (const observation of observations) {
- try {
- const observationLocator = stagehand.page
- .locator(observation.selector)
- .first();
- const observationHandle = await observationLocator.elementHandle();
- if (!observationHandle) {
- continue;
- }
-
- for (const { locatorStr, handle: candidateHandle } of possibleHandles) {
- const isSameNode = await observationHandle.evaluate(
- (node, otherNode) => node === otherNode,
- candidateHandle,
- );
- if (isSameNode) {
- foundMatch = true;
- matchedLocator = locatorStr;
- break;
- }
- }
-
- if (foundMatch) {
- break;
- }
- } catch (error) {
- console.warn(
- `Failed to check observation with selector ${observation.selector}:`,
- error.message,
- );
- continue;
- }
- }
-
- await stagehand.close();
-
- return {
- _success: foundMatch,
- matchedLocator,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/observe_iframes2.ts b/evals/tasks/observe_iframes2.ts
deleted file mode 100644
index e02ebc7ab..000000000
--- a/evals/tasks/observe_iframes2.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { Stagehand } from "@/dist";
-import { EvalFunction } from "@/types/evals";
-import { ObserveResult } from "@/types/stagehand";
-
-export const observe_iframes2: EvalFunction = async ({
- logger,
- stagehandConfig,
- debugUrl,
- sessionUrl,
-}) => {
- const stagehand = new Stagehand({
- ...stagehandConfig,
- });
- await stagehand.init();
-
- await stagehand.page.goto(
- "https://iframetester.com/?url=https://shopify.com",
- );
- await new Promise((resolve) => setTimeout(resolve, 5000));
-
- let observations: ObserveResult[];
- try {
- observations = await stagehand.page.observe({
- instruction: "find the main header of the page",
- });
- } catch (err) {
- await stagehand.close();
- return {
- _success: false,
- message: err.message,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- if (observations.length === 0) {
- await stagehand.close();
- return {
- _success: false,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- const possibleLocators = [`#iframe-window`, `body > header > h1`];
-
- const possibleHandles = [];
- for (const locatorStr of possibleLocators) {
- const locator = stagehand.page.locator(locatorStr);
- const handle = await locator.elementHandle();
- if (handle) {
- possibleHandles.push({ locatorStr, handle });
- }
- }
-
- let foundMatch = false;
- let matchedLocator: string | null = null;
-
- for (const observation of observations) {
- try {
- const observationLocator = stagehand.page
- .locator(observation.selector)
- .first();
- const observationHandle = await observationLocator.elementHandle();
- if (!observationHandle) {
- continue;
- }
-
- for (const { locatorStr, handle: candidateHandle } of possibleHandles) {
- const isSameNode = await observationHandle.evaluate(
- (node, otherNode) => node === otherNode,
- candidateHandle,
- );
- if (isSameNode) {
- foundMatch = true;
- matchedLocator = locatorStr;
- break;
- }
- }
-
- if (foundMatch) {
- break;
- }
- } catch (error) {
- console.warn(
- `Failed to check observation with selector ${observation.selector}:`,
- error.message,
- );
- continue;
- }
- }
-
- await stagehand.close();
-
- return {
- _success: foundMatch,
- matchedLocator,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/observe_simple_google_search.ts b/evals/tasks/observe_simple_google_search.ts
deleted file mode 100644
index 7e6fd1331..000000000
--- a/evals/tasks/observe_simple_google_search.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { performPlaywrightMethod } from "@/lib/a11y/utils";
-
-export const observe_simple_google_search: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://www.google.com");
-
- const observation1 = await stagehand.page.observe({
- instruction: "Find the search bar and enter 'OpenAI'",
- onlyVisible: false,
- returnAction: true,
- });
- console.log(observation1);
-
- if (observation1.length > 0) {
- const action1 = observation1[0];
- await performPlaywrightMethod(
- stagehand.page,
- stagehand.logger,
- action1.method,
- action1.arguments,
- action1.selector.replace("xpath=", ""),
- );
- }
- await stagehand.page.waitForTimeout(5000);
- const observation2 = await stagehand.page.observe({
- instruction: "Click the search button in the suggestions dropdown",
- onlyVisible: false,
- returnAction: true,
- });
- console.log(observation2);
-
- if (observation2.length > 0) {
- const action2 = observation2[0];
- await performPlaywrightMethod(
- stagehand.page,
- stagehand.logger,
- action2.method,
- action2.arguments,
- action2.selector.replace("xpath=", ""),
- );
- }
- await stagehand.page.waitForTimeout(5000);
-
- const expectedUrl = "https://www.google.com/search?q=OpenAI";
- const currentUrl = stagehand.page.url();
-
- await stagehand.close();
-
- return {
- _success: currentUrl.startsWith(expectedUrl),
- currentUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/observe_taxes.ts b/evals/tasks/observe_taxes.ts
deleted file mode 100644
index 3ba0b3676..000000000
--- a/evals/tasks/observe_taxes.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const observe_taxes: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://file.1040.com/estimate/");
-
- const observations = await stagehand.page.observe({
- instruction: "Find all the form input elements under the 'Income' section",
- });
-
- if (observations.length === 0) {
- await stagehand.close();
- return {
- _success: false,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- } else if (observations.length < 13) {
- await stagehand.close();
- return {
- _success: false,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- const expectedLocator = `#tpWages`;
-
- const expectedResult = await stagehand.page
- .locator(expectedLocator)
- .first()
- .innerText();
-
- let foundMatch = false;
- for (const observation of observations) {
- try {
- const observationResult = await stagehand.page
- .locator(observation.selector)
- .first()
- .innerText();
-
- if (observationResult === expectedResult) {
- foundMatch = true;
- break;
- }
- } catch (error) {
- console.warn(
- `Failed to check observation with selector ${observation.selector}:`,
- error.message,
- );
- continue;
- }
- }
-
- await stagehand.close();
-
- return {
- _success: foundMatch,
- expected: expectedResult,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/observe_vantechjournal.ts b/evals/tasks/observe_vantechjournal.ts
deleted file mode 100644
index c3eef6a71..000000000
--- a/evals/tasks/observe_vantechjournal.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const observe_vantechjournal: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://vantechjournal.com/archive?page=8");
- await stagehand.page.waitForTimeout(1000);
-
- const observations = await stagehand.page.observe({
- instruction: "find the button that takes us to the 11th page",
- });
-
- if (observations.length === 0) {
- await stagehand.close();
- return {
- _success: false,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- const expectedLocator = `a.rounded-lg:nth-child(8)`;
-
- const expectedResult = await stagehand.page.locator(expectedLocator);
-
- let foundMatch = false;
-
- for (const observation of observations) {
- try {
- const observationLocator = stagehand.page
- .locator(observation.selector)
- .first();
- const observationHandle = await observationLocator.elementHandle();
- const expectedHandle = await expectedResult.elementHandle();
-
- if (!observationHandle || !expectedHandle) {
- // Couldn’t get handles, skip
- continue;
- }
-
- const isSameNode = await observationHandle.evaluate(
- (node, otherNode) => node === otherNode,
- expectedHandle,
- );
-
- if (isSameNode) {
- foundMatch = true;
- break;
- }
- } catch (error) {
- console.warn(
- `Failed to check observation with selector ${observation.selector}:`,
- error.message,
- );
- continue;
- }
- }
-
- await stagehand.close();
-
- return {
- _success: foundMatch,
- expected: expectedResult,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/observe_yc_startup.ts b/evals/tasks/observe_yc_startup.ts
deleted file mode 100644
index bdc654da5..000000000
--- a/evals/tasks/observe_yc_startup.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const observe_yc_startup: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://www.ycombinator.com/companies");
- await stagehand.page.waitForLoadState("networkidle");
-
- const observations = await stagehand.page.observe({
- instruction:
- "Click the container element that holds links to each of the startup companies. The companies each have a name, a description, and a link to their website.",
- });
-
- if (observations.length === 0) {
- await stagehand.close();
- return {
- _success: false,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- const possibleLocators = [
- `div._rightCol_i9oky_592`,
- `div._section_i9oky_163._results_i9oky_343`,
- ];
-
- const possibleHandles = [];
- for (const locatorStr of possibleLocators) {
- const locator = stagehand.page.locator(locatorStr);
- const handle = await locator.elementHandle();
- if (handle) {
- possibleHandles.push({ locatorStr, handle });
- }
- }
-
- let foundMatch = false;
- let matchedLocator: string | null = null;
-
- for (const observation of observations) {
- try {
- const observationLocator = stagehand.page
- .locator(observation.selector)
- .first();
- const observationHandle = await observationLocator.elementHandle();
- if (!observationHandle) {
- continue;
- }
-
- for (const { locatorStr, handle: candidateHandle } of possibleHandles) {
- const isSameNode = await observationHandle.evaluate(
- (node, otherNode) => node === otherNode,
- candidateHandle,
- );
- if (isSameNode) {
- foundMatch = true;
- matchedLocator = locatorStr;
- break;
- }
- }
-
- if (foundMatch) {
- break;
- }
- } catch (error) {
- console.warn(
- `Failed to check observation with selector ${observation.selector}:`,
- error.message,
- );
- continue;
- }
- }
-
- await stagehand.close();
-
- return {
- _success: foundMatch,
- matchedLocator,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/panamcs.ts b/evals/tasks/panamcs.ts
deleted file mode 100644
index 2b68d096e..000000000
--- a/evals/tasks/panamcs.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const panamcs: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/panamcs/",
- );
-
- const observations = await stagehand.page.observe(
- "click the 'about us' link",
- );
-
- if (observations.length === 0) {
- await stagehand.close();
- return {
- _success: false,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- const expectedLocator = `#menu > li:nth-child(1) > a`;
-
- const expectedResult = await stagehand.page
- .locator(expectedLocator)
- .first()
- .innerText();
-
- let foundMatch = false;
- for (const observation of observations) {
- try {
- const observationResult = await stagehand.page
- .locator(observation.selector)
- .first()
- .innerText();
-
- if (observationResult === expectedResult) {
- foundMatch = true;
- break;
- }
- } catch (error) {
- console.warn(
- `Failed to check observation with selector ${observation.selector}:`,
- error.message,
- );
- continue;
- }
- }
-
- await stagehand.close();
-
- return {
- _success: foundMatch,
- expected: expectedResult,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/peeler_simple.ts b/evals/tasks/peeler_simple.ts
deleted file mode 100644
index 12628c6fa..000000000
--- a/evals/tasks/peeler_simple.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { StagehandEnvironmentError } from "@/types/stagehandErrors";
-
-const env: "BROWSERBASE" | "LOCAL" =
- process.env.EVAL_ENV?.toLowerCase() === "browserbase"
- ? "BROWSERBASE"
- : "LOCAL";
-
-export const peeler_simple: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- if (env === "BROWSERBASE") {
- throw new StagehandEnvironmentError(
- "BROWSERBASE",
- "LOCAL",
- "peeler_simple eval",
- );
- }
-
- await stagehand.page.goto(`file://${process.cwd()}/evals/assets/peeler.html`);
- await stagehand.page.act({ action: "add the peeler to cart" });
-
- const successMessageLocator = stagehand.page.locator(
- 'text="Congratulations, you have 1 A in your cart"',
- );
- const isVisible = await successMessageLocator.isVisible();
-
- await stagehand.close();
-
- return {
- _success: isVisible,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/prevChunk.ts b/evals/tasks/prevChunk.ts
deleted file mode 100644
index 0db2266bf..000000000
--- a/evals/tasks/prevChunk.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Stagehand } from "@/dist";
-import { EvalFunction } from "@/types/evals";
-
-export const prevChunk: EvalFunction = async ({
- logger,
- stagehandConfig,
- debugUrl,
- sessionUrl,
-}) => {
- const stagehand = new Stagehand({
- ...stagehandConfig,
- domSettleTimeoutMs: 3000,
- });
- await stagehand.init();
-
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/aigrant/",
- );
- await new Promise((resolve) => setTimeout(resolve, 2000));
- const { initialScrollTop, chunkHeight } = await stagehand.page.evaluate(
- () => {
- const halfPage = document.body.scrollHeight / 2;
-
- window.scrollTo({
- top: halfPage,
- left: 0,
- behavior: "instant",
- });
-
- const chunk = window.innerHeight;
-
- return {
- initialScrollTop: window.scrollY,
- chunkHeight: chunk,
- };
- },
- );
- await new Promise((resolve) => setTimeout(resolve, 2000));
- await stagehand.page.act({
- action: "scroll up one chunk",
- });
-
- await new Promise((resolve) => setTimeout(resolve, 5000));
-
- const finalScrollTop = await stagehand.page.evaluate(() => window.scrollY);
-
- await stagehand.close();
-
- const actualDiff = initialScrollTop - finalScrollTop;
- const threshold = 20; // px tolerance
- const scrolledOneChunk = Math.abs(actualDiff - chunkHeight) <= threshold;
-
- const evaluationResult = scrolledOneChunk
- ? {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- message: `Successfully scrolled ~one chunk UP: expected ~${chunkHeight}, got ${actualDiff}.`,
- }
- : {
- _success: false,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- message: `Scroll difference expected ~${chunkHeight} but only scrolled ${actualDiff}.`,
- };
-
- return evaluationResult;
-};
diff --git a/evals/tasks/rakuten_jp.ts b/evals/tasks/rakuten_jp.ts
deleted file mode 100644
index cf8ea55fe..000000000
--- a/evals/tasks/rakuten_jp.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const rakuten_jp: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://www.rakuten.co.jp/");
- await stagehand.page.act({ action: "click on online supermarket" });
-
- await stagehand.page.act({ action: "if there is a popup, close it" });
-
- await stagehand.page.act({
- action: "navigate to Inageya Online Supermarket",
- });
- await stagehand.page.act({ action: "click the search bar input" });
- await stagehand.page.act({ action: "search for '香菜'" });
-
- const url = stagehand.page.url();
- const successUrl =
- "https://netsuper.rakuten.co.jp/inageya/search/?keyword=%E9%A6%99%E8%8F%9C";
-
- await stagehand.close();
-
- return {
- _success: url === successUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/sciquest.ts b/evals/tasks/sciquest.ts
deleted file mode 100644
index 42bc535d7..000000000
--- a/evals/tasks/sciquest.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const sciquest: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://bids.sciquest.com/apps/Router/PublicEvent?tab=PHX_NAV_SourcingAllOpps&CustomerOrg=StateOfUtah",
- );
-
- await stagehand.page.act({
- action: 'Click on the "Closed" tab',
- });
-
- const result = await stagehand.page.extract({
- instruction:
- "Extract the total number of results that the search produced. Not the number of results displayed on the page.",
- schema: z.object({
- total_results: z.string(),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const { total_results } = result;
-
- const expectedNumber = 12637;
- const extractedNumber = parseInt(total_results.replace(/[^\d]/g, ""), 10);
-
- const isWithinRange =
- extractedNumber >= expectedNumber - 1000 &&
- extractedNumber <= expectedNumber + 1000;
-
- if (!isWithinRange) {
- logger.error({
- message: "Total number of results is not within the expected range",
- level: 0,
- auxiliary: {
- expected: {
- value: `${expectedNumber} ± 1000`,
- type: "string",
- },
- actual: {
- value: extractedNumber.toString(),
- type: "integer",
- },
- },
- });
- return {
- _success: false,
- error: "Total number of results is not within the expected range",
- extractedNumber,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- return {
- _success: true,
- extractedNumber,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/scroll_50.ts b/evals/tasks/scroll_50.ts
deleted file mode 100644
index c6abb53c3..000000000
--- a/evals/tasks/scroll_50.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const scroll_50: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/aigrant/",
- );
- await stagehand.page.act({
- action: "Scroll 50% down the page",
- });
-
- await new Promise((resolve) => setTimeout(resolve, 5000));
-
- // Get the current scroll position and total scroll height
- const scrollInfo = await stagehand.page.evaluate(() => {
- return {
- scrollTop: window.scrollY + window.innerHeight / 2,
- scrollHeight: document.documentElement.scrollHeight,
- };
- });
-
- await stagehand.close();
-
- const halfwayScroll = scrollInfo.scrollHeight / 2;
- const halfwayReached = Math.abs(scrollInfo.scrollTop - halfwayScroll) <= 200;
- const evaluationResult = halfwayReached
- ? {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- }
- : {
- _success: false,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- message: `Scroll position (${scrollInfo.scrollTop}px) is not halfway down the page (${halfwayScroll}px).`,
- };
-
- return evaluationResult;
-};
diff --git a/evals/tasks/scroll_75.ts b/evals/tasks/scroll_75.ts
deleted file mode 100644
index 3959837ca..000000000
--- a/evals/tasks/scroll_75.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { Stagehand } from "@/dist";
-import { EvalFunction } from "@/types/evals";
-
-export const scroll_75: EvalFunction = async ({
- logger,
- stagehandConfig,
- debugUrl,
- sessionUrl,
-}) => {
- const stagehand = new Stagehand({
- ...stagehandConfig,
- domSettleTimeoutMs: 3000,
- });
- await stagehand.init();
-
- await stagehand.page.goto(
- "https://browserbase.github.io/stagehand-eval-sites/sites/aigrant/",
- );
- await stagehand.page.act({
- action: "Scroll 75% down the page",
- });
-
- await new Promise((resolve) => setTimeout(resolve, 5000));
-
- // Get the current scroll position and total scroll height
- const scrollInfo = await stagehand.page.evaluate(() => {
- return {
- scrollTop: window.scrollY + window.innerHeight * 0.75,
- scrollHeight: document.documentElement.scrollHeight,
- };
- });
-
- await stagehand.close();
-
- const threeQuartersScroll = scrollInfo.scrollHeight * 0.75;
- const threeQuartersReached =
- Math.abs(scrollInfo.scrollTop - threeQuartersScroll) <= 200;
- const evaluationResult = threeQuartersReached
- ? {
- _success: true,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- }
- : {
- _success: false,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- message: `Scroll position (${scrollInfo.scrollTop}px) is not three quarters down the page (${threeQuartersScroll}px).`,
- };
-
- return evaluationResult;
-};
diff --git a/evals/tasks/simple_google_search.ts b/evals/tasks/simple_google_search.ts
deleted file mode 100644
index e6a681200..000000000
--- a/evals/tasks/simple_google_search.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const simple_google_search: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://www.google.com");
-
- await stagehand.page.act({
- action: 'type "OpenAI" into the search bar',
- });
-
- await stagehand.page.act("click the search button");
-
- const expectedUrl = "https://www.google.com/search?q=OpenAI";
- const currentUrl = stagehand.page.url();
-
- await stagehand.close();
-
- return {
- _success: currentUrl.startsWith(expectedUrl),
- currentUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/stock_x.ts b/evals/tasks/stock_x.ts
deleted file mode 100644
index b1459b0a6..000000000
--- a/evals/tasks/stock_x.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const stock_x: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto(
- "https://stockx.com/air-jordan-3-retro-black-cement-2024",
- );
-
- await stagehand.page.waitForTimeout(3000);
-
- await stagehand.page.act({
- action: "click on Jordan 3 Retro Crimson in the related products",
- });
-
- await stagehand.page.waitForTimeout(2000);
- const currentUrl = stagehand.page.url();
- const expectedUrlPrefix = "https://stockx.com/jordan-3-retro-crimson";
-
- await stagehand.close();
-
- return {
- _success: currentUrl.startsWith(expectedUrlPrefix),
- currentUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/ted_talk.ts b/evals/tasks/ted_talk.ts
deleted file mode 100644
index d288df8f6..000000000
--- a/evals/tasks/ted_talk.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { normalizeString } from "@/evals/utils";
-import { z } from "zod";
-
-export const ted_talk: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto(
- "https://www.ted.com/talks/sir_ken_robinson_do_schools_kill_creativity",
- {
- waitUntil: "domcontentloaded",
- },
- );
- await stagehand.page.act({
- action:
- "Click the link that takes you to the page about the 'Culture' topic",
- });
-
- const playlists = await stagehand.page.extract({
- instruction:
- "Extract the video playlist titles and the number of talks in each playlist. This info is in the Video Playlists about Culture section of the webpage.",
- schema: z.object({
- playlists: z
- .array(
- z.object({
- title: z.string().describe("Title of the playlist"),
- num_talks: z.number().describe("Number of talks in the playlist"),
- }),
- )
- .describe("List of culture video playlists"),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const expectedPlaylists = [
- {
- title: "Talks that celebrate the boundless creativity of an open mind",
- num_talks: 6,
- },
- {
- title: "Little-known big history",
- num_talks: 15,
- },
- {
- title: "Extraordinary, larger-than-life art",
- num_talks: 10,
- },
- {
- title: "How perfectionism fails us",
- num_talks: 4,
- },
- ];
-
- if (!playlists.playlists || playlists.playlists.length === 0) {
- logger.error({
- message: "Failed to extract playlists on culture",
- level: 0,
- });
-
- return {
- _success: false,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- const missingPlaylists = expectedPlaylists.filter((expected) =>
- playlists.playlists.every(
- (extracted) =>
- normalizeString(extracted.title) !== normalizeString(expected.title) ||
- extracted.num_talks !== expected.num_talks,
- ),
- );
-
- if (missingPlaylists.length > 0) {
- logger.error({
- message: "Extracted playlists do not match expected playlists",
- level: 0,
- auxiliary: {
- missing: {
- value: JSON.stringify(missingPlaylists),
- type: "object",
- },
- extracted: {
- value: JSON.stringify(playlists.playlists),
- type: "object",
- },
- },
- });
-
- return {
- _success: false,
- error: "Extracted playlists do not match expected playlists",
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
- }
-
- return {
- _success: true,
- playlists: playlists.playlists,
- logs: logger.getLogs(),
- debugUrl,
- sessionUrl,
- };
-};
diff --git a/evals/tasks/vanta_h.ts b/evals/tasks/vanta_h.ts
deleted file mode 100644
index dba21d303..000000000
--- a/evals/tasks/vanta_h.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const vanta_h: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://www.vanta.com/");
-
- const observations = await stagehand.page.observe(
- "click the buy now button if it is available",
- );
-
- await stagehand.close();
-
- // we should have no saved observation since the element shouldn't exist
- return {
- _success: observations.length === 0,
- observations,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/vantechjournal.ts b/evals/tasks/vantechjournal.ts
deleted file mode 100644
index c176142b0..000000000
--- a/evals/tasks/vantechjournal.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const vantechjournal: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto("https://vantechjournal.com/");
-
- await stagehand.page.act({
- action: "click on page 8. do not click the next button",
- });
-
- const expectedUrl = "https://vantechjournal.com/archive?page=8";
- const currentUrl = stagehand.page.url();
-
- await stagehand.close();
-
- return {
- _success: currentUrl === expectedUrl,
- currentUrl,
- expectedUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/wichita.ts b/evals/tasks/wichita.ts
deleted file mode 100644
index fe645b480..000000000
--- a/evals/tasks/wichita.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-import { z } from "zod";
-
-export const wichita: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
- useTextExtract,
-}) => {
- await stagehand.page.goto("https://www.wichitafallstx.gov/Bids.aspx");
-
- await stagehand.page.act({
- action: 'Click on "Show Closed/Awarded/Cancelled bids"',
- });
-
- const result = await stagehand.page.extract({
- instruction: "Extract the total number of bids that the search produced.",
- schema: z.object({
- total_results: z.string(),
- }),
- useTextExtract,
- });
-
- await stagehand.close();
-
- const { total_results } = result;
-
- const expectedNumber = 405;
- const extractedNumber = parseInt(total_results.replace(/[^\d]/g, ""), 10);
-
- const isWithinRange =
- extractedNumber >= expectedNumber - 10 &&
- extractedNumber <= expectedNumber + 10;
-
- if (!isWithinRange) {
- logger.error({
- message: "Total number of results is not within the expected range",
- level: 0,
- auxiliary: {
- expected: {
- value: `${expectedNumber} ± 10`,
- type: "string",
- },
- actual: {
- value: extractedNumber.toString(),
- type: "integer",
- },
- },
- });
- return {
- _success: false,
- error: "Total number of results is not within the expected range",
- extractedNumber,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
- }
-
- return {
- _success: true,
- extractedNumber,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/evals/tasks/wikipedia.ts b/evals/tasks/wikipedia.ts
deleted file mode 100644
index 5dad05d2f..000000000
--- a/evals/tasks/wikipedia.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { EvalFunction } from "@/types/evals";
-
-export const wikipedia: EvalFunction = async ({
- debugUrl,
- sessionUrl,
- stagehand,
- logger,
-}) => {
- await stagehand.page.goto(`https://en.wikipedia.org/wiki/Baseball`);
- await stagehand.page.act({
- action: 'click the "hit and run" link in this article',
- timeoutMs: 360_000,
- });
-
- const url = "https://en.wikipedia.org/wiki/Hit_and_run_(baseball)";
- const currentUrl = stagehand.page.url();
-
- await stagehand.close();
-
- return {
- _success: currentUrl === url,
- expected: url,
- actual: currentUrl,
- debugUrl,
- sessionUrl,
- logs: logger.getLogs(),
- };
-};
diff --git a/examples/actionable_observe_example.ts b/examples/actionable_observe_example.ts
deleted file mode 100644
index 5fa55d80c..000000000
--- a/examples/actionable_observe_example.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * This file is meant to be used as a scratchpad for trying out actionable observe.
- * To create a Stagehand project with best practices and configuration, run:
- *
- * npx create-browser-app@latest my-browser-app
- */
-
-import { Stagehand } from "@/dist";
-import stagehandConfig from "@/stagehand.config";
-
-async function example() {
- const stagehand = new Stagehand(stagehandConfig);
- await stagehand.init();
- await stagehand.page.goto("https://www.apartments.com/san-francisco-ca/");
-
- await new Promise((resolve) => setTimeout(resolve, 3000));
- const observations1 = await stagehand.page.observe({
- instruction: "find the 'all filters' button",
- });
- await stagehand.page.act(observations1[0]);
-
- await new Promise((resolve) => setTimeout(resolve, 3000));
- const observations2 = await stagehand.page.observe({
- instruction: "find the '1+' button in the 'beds' section",
- });
- await stagehand.page.act(observations2[0]);
-
- await new Promise((resolve) => setTimeout(resolve, 3000));
- const observations3 = await stagehand.page.observe({
- instruction: "find the 'apartments' button in the 'home type' section",
- });
- await stagehand.page.act(observations3[0]);
-
- await new Promise((resolve) => setTimeout(resolve, 3000));
- const observations4 = await stagehand.page.observe({
- instruction: "find the pet policy dropdown to click on.",
- });
- await stagehand.page.act(observations4[0]);
-
- await new Promise((resolve) => setTimeout(resolve, 3000));
- const observations5 = await stagehand.page.observe({
- instruction: "find the 'Dog Friendly' option to click on",
- });
- await stagehand.page.act(observations5[0]);
-
- await new Promise((resolve) => setTimeout(resolve, 3000));
- const observations6 = await stagehand.page.observe({
- instruction: "find the 'see results' section",
- });
- await stagehand.page.act(observations6[0]);
-
- const currentUrl = await stagehand.page.url();
- await stagehand.close();
- if (
- currentUrl.includes(
- "https://www.apartments.com/apartments/san-francisco-ca/min-1-bedrooms-pet-friendly-dog/",
- )
- ) {
- console.log("✅ Success! we made it to the correct page");
- } else {
- console.log(
- "❌ Whoops, looks like we didn't make it to the correct page. " +
- "\nThanks for testing out this new Stagehand feature!" +
- "\nReach us on Slack if you have any feedback/questions/suggestions!",
- );
- }
-}
-
-(async () => {
- await example();
-})();
diff --git a/examples/ai_sdk_example.ts b/examples/ai_sdk_example.ts
deleted file mode 100644
index b650de5c1..000000000
--- a/examples/ai_sdk_example.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { openai } from "@ai-sdk/openai";
-import { Stagehand } from "@/dist";
-import { AISdkClient } from "./external_clients/aisdk";
-import StagehandConfig from "@/stagehand.config";
-import { z } from "zod";
-
-async function example() {
- const stagehand = new Stagehand({
- ...StagehandConfig,
- llmClient: new AISdkClient({
- model: openai("gpt-4o"),
- }),
- });
-
- await stagehand.init();
- await stagehand.page.goto("https://news.ycombinator.com");
-
- const { story } = await stagehand.page.extract({
- instruction: "extract the title of the top story on the page",
- schema: z.object({
- story: z.string().describe("the top story on the page"),
- }),
- });
-
- console.log("The top story is:", story);
-
- await stagehand.page.act("click the first story");
-
- await stagehand.close();
-}
-
-(async () => {
- await example();
-})();
diff --git a/examples/cua-example.ts b/examples/cua-example.ts
deleted file mode 100644
index e48eaf25b..000000000
--- a/examples/cua-example.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import { Stagehand } from "@/dist";
-import dotenv from "dotenv";
-import StagehandConfig from "@/stagehand.config";
-import chalk from "chalk";
-
-// Load environment variables
-dotenv.config();
-
-async function main() {
- console.log(
- `\n${chalk.bold("Stagehand 🤘 Computer Use Agent (CUA) Demo")}\n`,
- );
-
- // Initialize Stagehand
- console.log(`${chalk.cyan("→")} Initializing Stagehand...`);
- const stagehand = new Stagehand({
- ...StagehandConfig,
- });
-
- await stagehand.init();
- console.log(`${chalk.green("✓")} Stagehand initialized`);
-
- try {
- const page = stagehand.page;
-
- console.log(`\n${chalk.magenta.bold("⚡ First Agent Execution")}`);
-
- const agent = stagehand.agent({
- provider: "openai",
- model: "computer-use-preview",
- instructions: `You are a helpful assistant that can use a web browser.
- You are currently on the following page: ${page.url()}.
- Do not ask follow up questions, the user will trust your judgement.`,
- options: {
- apiKey: process.env.OPENAI_API_KEY,
- },
- });
-
- console.log(`${chalk.yellow("→")} Navigating to Google...`);
- await stagehand.page.goto("https://www.google.com");
- console.log(`${chalk.green("✓")} Loaded: ${chalk.dim(page.url())}`);
-
- const firstInstruction =
- "Search for openai news on google and extract the name of the first 3 results";
- console.log(
- `${chalk.cyan("↳")} Instruction: ${chalk.white(firstInstruction)}`,
- );
-
- const result1 = await agent.execute(firstInstruction);
-
- console.log(`${chalk.green("✓")} Execution complete`);
- console.log(`${chalk.yellow("⤷")} Result:`);
- console.log(chalk.white(JSON.stringify(result1, null, 2)));
-
- console.log(`\n${chalk.magenta.bold("⚡ Second Agent Execution")}`);
-
- console.log(
- `\n${chalk.yellow("→")} Navigating to Browserbase careers page...`,
- );
- await page.goto("https://www.browserbase.com/careers");
- console.log(`${chalk.green("✓")} Loaded: ${chalk.dim(page.url())}`);
-
- const instruction =
- "Apply for the first engineer position with mock data. Don't submit the form.";
- console.log(`${chalk.cyan("↳")} Instruction: ${chalk.white(instruction)}`);
-
- const result = await agent.execute({
- instruction,
- maxSteps: 20,
- });
-
- console.log(`${chalk.green("✓")} Execution complete`);
- console.log(`${chalk.yellow("⤷")} Result:`);
- console.log(chalk.white(JSON.stringify(result, null, 2)));
- } catch (error) {
- console.log(`${chalk.red("✗")} Error: ${error}`);
- if (error instanceof Error && error.stack) {
- console.log(chalk.dim(error.stack.split("\n").slice(1).join("\n")));
- }
- } finally {
- // Close the browser
- console.log(`\n${chalk.yellow("→")} Closing browser...`);
- await stagehand.close();
- console.log(`${chalk.green("✓")} Browser closed\n`);
- }
-}
-
-main().catch((error) => {
- console.log(`${chalk.red("✗")} Unhandled error in main function`);
- console.log(chalk.red(error));
-});
diff --git a/examples/debugUrl.ts b/examples/debugUrl.ts
deleted file mode 100644
index e54bb1e2a..000000000
--- a/examples/debugUrl.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Stagehand } from "@/dist";
-
-async function debug(url: string) {
- const stagehand = new Stagehand({
- env: "LOCAL",
- verbose: 2,
- localBrowserLaunchOptions: {
- headless: true,
- },
- });
- await stagehand.init();
- await stagehand.page.goto(url);
-}
-
-(async () => {
- const url = process.argv.find((arg) => arg.startsWith("--url="));
- if (!url) {
- console.error("No URL flag provided. Usage: --url=https://example.com");
- process.exit(1);
- }
- const targetUrl = url.split("=")[1];
- console.log(`Navigating to: ${targetUrl}`);
- await debug(targetUrl);
-})();
diff --git a/examples/example.ts b/examples/example.ts
deleted file mode 100644
index 372d72e5c..000000000
--- a/examples/example.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * This file is meant to be used as a scratchpad for developing new evals.
- * To create a Stagehand project with best practices and configuration, run:
- *
- * npx create-browser-app@latest my-browser-app
- */
-
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/stagehand.config";
-
-async function example() {
- const stagehand = new Stagehand({
- ...StagehandConfig,
- });
- await stagehand.init();
- await stagehand.page.goto("https://docs.stagehand.dev");
- /**
- * Add your code here!
- */
- await stagehand.close();
-}
-
-(async () => {
- await example();
-})();
diff --git a/examples/external_clients/aisdk.ts b/examples/external_clients/aisdk.ts
deleted file mode 100644
index 1d72d984f..000000000
--- a/examples/external_clients/aisdk.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import {
- CoreAssistantMessage,
- CoreMessage,
- CoreSystemMessage,
- CoreTool,
- CoreUserMessage,
- generateObject,
- generateText,
- ImagePart,
- LanguageModel,
- TextPart,
-} from "ai";
-import { CreateChatCompletionOptions, LLMClient, AvailableModel } from "@/dist";
-import { ChatCompletion } from "openai/resources";
-
-export class AISdkClient extends LLMClient {
- public type = "aisdk" as const;
- private model: LanguageModel;
-
- constructor({ model }: { model: LanguageModel }) {
- super(model.modelId as AvailableModel);
- this.model = model;
- }
-
- async createChatCompletion({
- options,
- }: CreateChatCompletionOptions): Promise {
- const formattedMessages: CoreMessage[] = options.messages.map((message) => {
- if (Array.isArray(message.content)) {
- if (message.role === "system") {
- const systemMessage: CoreSystemMessage = {
- role: "system",
- content: message.content
- .map((c) => ("text" in c ? c.text : ""))
- .join("\n"),
- };
- return systemMessage;
- }
-
- const contentParts = message.content.map((content) => {
- if ("image_url" in content) {
- const imageContent: ImagePart = {
- type: "image",
- image: content.image_url.url,
- };
- return imageContent;
- } else {
- const textContent: TextPart = {
- type: "text",
- text: content.text,
- };
- return textContent;
- }
- });
-
- if (message.role === "user") {
- const userMessage: CoreUserMessage = {
- role: "user",
- content: contentParts,
- };
- return userMessage;
- } else {
- const textOnlyParts = contentParts.map((part) => ({
- type: "text" as const,
- text: part.type === "image" ? "[Image]" : part.text,
- }));
- const assistantMessage: CoreAssistantMessage = {
- role: "assistant",
- content: textOnlyParts,
- };
- return assistantMessage;
- }
- }
-
- return {
- role: message.role,
- content: message.content,
- };
- });
-
- if (options.response_model) {
- const response = await generateObject({
- model: this.model,
- messages: formattedMessages,
- schema: options.response_model.schema,
- });
-
- 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) {
- tools[rawTool.name] = {
- description: rawTool.description,
- parameters: rawTool.parameters,
- };
- }
-
- const response = await generateText({
- model: this.model,
- messages: formattedMessages,
- tools,
- });
-
- 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/form_filling_sensible_cerebras.ts b/examples/form_filling_sensible_cerebras.ts
deleted file mode 100644
index 862a3e471..000000000
--- a/examples/form_filling_sensible_cerebras.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/stagehand.config";
-import { CerebrasClient } from "../lib/llm/CerebrasClient";
-import chalk from "chalk";
-
-async function formFillingSensible() {
- const stagehand = new Stagehand({
- ...StagehandConfig,
- env: "LOCAL",
- llmClient: new CerebrasClient({
- modelName: "cerebras-llama-3.3-70b",
- clientOptions: {
- apiKey: process.env.CEREBRAS_API_KEY,
- },
- logger: console.log,
- }),
- });
- await stagehand.init();
-
- // Block manifest worker to prevent PWA installation popup.
- // This is necessary because the website prompts the user to install the PWA and prevents form filling.
- await stagehand.page.route("**/manifest.json", (route) => route.abort());
-
- // Go to the website and wait for it to load
- await stagehand.page.goto("https://file.1040.com/estimate/", {
- waitUntil: "networkidle",
- timeout: 30000,
- });
-
- // Observe the form fields with suggested actions
- const observed = await stagehand.page.observe({
- instruction:
- "fill all the form fields in the page with mock data. In the description inlcude the field name",
- returnAction: true,
- });
-
- // Uncomment the following snippet to see the stagehand candidate suggestions (initial)
- console.log(
- `${chalk.green("Observe:")} Form fields found:\n${observed
- .map((r) => `${chalk.yellow(r.description)} -> ${chalk.gray(r.selector)}`)
- .join("\n")}`,
- );
-
- // Create a mapping of 1+ keywords in the form fields to standardize field names
- const mapping = (description: string): string | null => {
- const keywords: { [key: string]: string[] } = {
- age: ["old"],
- dependentsUnder17: ["under age 17", "child", "minor"],
- dependents17to23: ["17-23", "school", "student"],
- wages: ["wages", "W-2 Box 1"],
- federalTax: ["federal tax", "Box 2"],
- stateTax: ["state tax", "Box 17"],
- };
-
- for (const [key, terms] of Object.entries(keywords)) {
- if (terms.some((term) => description.toLowerCase().includes(term))) {
- return key;
- }
- }
- return null;
- };
-
- // Fill the form fields with sensible data. This data will only be used in your session and not be shared with LLM providers/external APIs.
- const userInputs: { [key: string]: string } = {
- age: "26",
- dependentsUnder17: "1",
- wages: "54321",
- federalTax: "8345",
- stateTax: "2222",
- };
-
- const updatedFields = observed.map((candidate) => {
- const key = mapping(candidate.description);
- if (key && userInputs[key]) {
- candidate.arguments = [userInputs[key]];
- }
- return candidate;
- });
- // List of sensible-data candidates
- console.log(
- `\n${chalk.green("Sensible Data form inputs:")} Form fields to be filled:\n${updatedFields
- .map(
- (r) =>
- `${chalk.yellow(r.description)} -> ${chalk.blue(r.arguments?.[0] || "no value")}`,
- )
- .join("\n")}`,
- );
-
- // Fill all the form fields with the sensible candidates
- for (const candidate of updatedFields) {
- await stagehand.page.act(candidate);
- }
-}
-
-(async () => {
- await formFillingSensible();
-})();
diff --git a/examples/form_filling_sensible_groq.ts b/examples/form_filling_sensible_groq.ts
deleted file mode 100644
index cf885e215..000000000
--- a/examples/form_filling_sensible_groq.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import { Stagehand } from "@/dist";
-import { GroqClient } from "@/lib/llm/GroqClient";
-import StagehandConfig from "@/stagehand.config";
-import chalk from "chalk";
-
-async function formFillingSensible() {
- const stagehand = new Stagehand({
- ...StagehandConfig,
- env: "LOCAL",
- llmClient: new GroqClient({
- modelName: "groq-llama-3.3-70b-versatile",
- clientOptions: {
- apiKey: process.env.GROQ_API_KEY,
- },
- logger: console.log,
- }),
- });
- await stagehand.init();
-
- // Block manifest worker to prevent PWA installation popup.
- // This is necessary because the website prompts the user to install the PWA and prevents form filling.
- await stagehand.page.route("**/manifest.json", (route) => route.abort());
-
- // Go to the website and wait for it to load
- await stagehand.page.goto("https://file.1040.com/estimate/", {
- waitUntil: "networkidle",
- timeout: 30000,
- });
-
- // Observe the form fields with suggested actions
- const observed = await stagehand.page.observe({
- instruction:
- "fill all the form fields in the page with mock data. In the description inlcude the field name",
- returnAction: true,
- });
-
- // Uncomment the following snippet to see the stagehand candidate suggestions (initial)
- console.log(
- `${chalk.green("Observe:")} Form fields found:\n${observed
- .map((r) => `${chalk.yellow(r.description)} -> ${chalk.gray(r.selector)}`)
- .join("\n")}`,
- );
-
- // Create a mapping of 1+ keywords in the form fields to standardize field names
- const mapping = (description: string): string | null => {
- const keywords: { [key: string]: string[] } = {
- age: ["old"],
- dependentsUnder17: ["under age 17", "child", "minor"],
- dependents17to23: ["17-23", "school", "student"],
- wages: ["wages", "W-2 Box 1"],
- federalTax: ["federal tax", "Box 2"],
- stateTax: ["state tax", "Box 17"],
- };
-
- for (const [key, terms] of Object.entries(keywords)) {
- if (terms.some((term) => description.toLowerCase().includes(term))) {
- return key;
- }
- }
- return null;
- };
-
- // Fill the form fields with sensible data. This data will only be used in your session and not be shared with LLM providers/external APIs.
- const userInputs: { [key: string]: string } = {
- age: "26",
- dependentsUnder17: "1",
- wages: "54321",
- federalTax: "8345",
- stateTax: "2222",
- };
-
- const updatedFields = observed.map((candidate) => {
- const key = mapping(candidate.description);
- if (key && userInputs[key]) {
- candidate.arguments = [userInputs[key]];
- }
- return candidate;
- });
- // List of sensible-data candidates
- console.log(
- `\n${chalk.green("Sensible Data form inputs:")} Form fields to be filled:\n${updatedFields
- .map(
- (r) =>
- `${chalk.yellow(r.description)} -> ${chalk.blue(r.arguments?.[0] || "no value")}`,
- )
- .join("\n")}`,
- );
-
- // Fill all the form fields with the sensible candidates
- for (const candidate of updatedFields) {
- await stagehand.page.act(candidate);
- }
-}
-
-(async () => {
- await formFillingSensible();
-})();
diff --git a/examples/google_enter.ts b/examples/google_enter.ts
deleted file mode 100644
index affa1eb93..000000000
--- a/examples/google_enter.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * This file is meant to be used as a scratchpad for developing new evals.
- * To create a Stagehand project with best practices and configuration, run:
- *
- * npx create-browser-app@latest my-browser-app
- */
-
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/stagehand.config";
-
-async function example() {
- const stagehand = new Stagehand({
- ...StagehandConfig,
- });
- await stagehand.init();
- const page = stagehand.page;
- await page.goto("https://google.com");
- await page.act("type in 'Browserbase'");
- await page.act("press enter");
- await stagehand.close();
-}
-
-(async () => {
- await example();
-})();
diff --git a/examples/popup.ts b/examples/popup.ts
deleted file mode 100644
index 72ec0369e..000000000
--- a/examples/popup.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * This file is meant to be used as a scratchpad for developing new evals.
- * To create a Stagehand project with best practices and configuration, run:
- *
- * npx create-browser-app@latest my-browser-app
- */
-
-import { ObserveResult, Stagehand } from "@/dist";
-import StagehandConfig from "@/stagehand.config";
-
-async function example() {
- const stagehand = new Stagehand(StagehandConfig);
- await stagehand.init();
-
- const page = await stagehand.page;
-
- let observePromise: Promise;
-
- page.on("popup", async (newPage) => {
- observePromise = newPage.observe({
- instruction: "return all the next possible actions from the page",
- });
- });
-
- await page.goto(
- "https://docs.browserbase.com/integrations/crew-ai/introduction",
- );
-
- await page.click(
- "#content-area > div.relative.mt-8.prose.prose-gray.dark\\:prose-invert > p:nth-child(2) > a",
- );
-
- await page.waitForTimeout(5000);
-
- if (observePromise) {
- const observeResult = await observePromise;
-
- console.log("Observed", observeResult.length, "actions");
- }
-
- await stagehand.close();
-}
-
-(async () => {
- await example();
-})();
diff --git a/examples/try_wordle.ts b/examples/try_wordle.ts
deleted file mode 100644
index a8f1fe22a..000000000
--- a/examples/try_wordle.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/stagehand.config";
-
-async function example() {
- const stagehand = new Stagehand({
- ...StagehandConfig,
- });
- await stagehand.init();
- const page = stagehand.page;
- await page.goto("https://www.nytimes.com/games/wordle/index.html");
- await page.act("click 'Continue'");
- await page.act("click 'Play'");
- await page.act("click cross sign on top right of 'How To Play' card");
- const word = "WORDS";
- for (const letter of word) {
- await page.act(`press ${letter}`);
- }
- await page.act("press enter");
- await stagehand.close();
-}
-
-(async () => {
- await example();
-})();
diff --git a/lib/StagehandContext.ts b/lib/StagehandContext.ts
deleted file mode 100644
index 5d8bf4e66..000000000
--- a/lib/StagehandContext.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import type {
- BrowserContext as PlaywrightContext,
- Page as PlaywrightPage,
-} from "@playwright/test";
-import { Stagehand } from "./index";
-import { StagehandPage } from "./StagehandPage";
-import { Page } from "../types/page";
-import { EnhancedContext } from "../types/context";
-
-export class StagehandContext {
- private readonly stagehand: Stagehand;
- private readonly intContext: EnhancedContext;
- private pageMap: WeakMap;
- private activeStagehandPage: StagehandPage | null = null;
-
- private constructor(context: PlaywrightContext, stagehand: Stagehand) {
- this.stagehand = stagehand;
- this.pageMap = new WeakMap();
-
- // Create proxy around the context
- this.intContext = new Proxy(context, {
- get: (target, prop) => {
- if (prop === "newPage") {
- return async (): Promise => {
- const pwPage = await target.newPage();
- const stagehandPage = await this.createStagehandPage(pwPage);
- // Set as active page when created
- this.setActivePage(stagehandPage);
- return stagehandPage.page;
- };
- }
- if (prop === "pages") {
- return (): Page[] => {
- const pwPages = target.pages();
- // Convert all pages to StagehandPages synchronously
- return pwPages.map((pwPage: PlaywrightPage) => {
- let stagehandPage = this.pageMap.get(pwPage);
- if (!stagehandPage) {
- // Create a new StagehandPage and store it in the map
- stagehandPage = new StagehandPage(
- pwPage,
- this.stagehand,
- this,
- this.stagehand.llmClient,
- this.stagehand.userProvidedInstructions,
- this.stagehand.apiClient,
- this.stagehand.waitForCaptchaSolves,
- );
- this.pageMap.set(pwPage, stagehandPage);
- }
- return stagehandPage.page;
- });
- };
- }
- return target[prop as keyof PlaywrightContext];
- },
- }) as unknown as EnhancedContext;
- }
-
- private async createStagehandPage(
- page: PlaywrightPage,
- ): Promise {
- const stagehandPage = await new StagehandPage(
- page,
- this.stagehand,
- this,
- this.stagehand.llmClient,
- this.stagehand.userProvidedInstructions,
- this.stagehand.apiClient,
- this.stagehand.waitForCaptchaSolves,
- ).init();
- this.pageMap.set(page, stagehandPage);
- return stagehandPage;
- }
-
- static async init(
- context: PlaywrightContext,
- stagehand: Stagehand,
- ): Promise {
- const instance = new StagehandContext(context, stagehand);
-
- // Initialize existing pages
- const existingPages = context.pages();
- for (const page of existingPages) {
- const stagehandPage = await instance.createStagehandPage(page);
- // Set the first page as active
- if (!instance.activeStagehandPage) {
- instance.setActivePage(stagehandPage);
- }
- }
-
- return instance;
- }
-
- public get context(): EnhancedContext {
- return this.intContext;
- }
-
- public async getStagehandPage(page: PlaywrightPage): Promise {
- let stagehandPage = this.pageMap.get(page);
- if (!stagehandPage) {
- stagehandPage = await this.createStagehandPage(page);
- }
- // Update active page when getting a page
- this.setActivePage(stagehandPage);
- return stagehandPage;
- }
-
- public async getStagehandPages(): Promise {
- const pwPages = this.intContext.pages();
- return Promise.all(
- pwPages.map((page: PlaywrightPage) => this.getStagehandPage(page)),
- );
- }
-
- public setActivePage(page: StagehandPage): void {
- this.activeStagehandPage = page;
- // Update the stagehand's active page reference
- this.stagehand["setActivePage"](page);
- }
-
- public getActivePage(): StagehandPage | null {
- return this.activeStagehandPage;
- }
-}
diff --git a/lib/StagehandPage.ts b/lib/StagehandPage.ts
deleted file mode 100644
index 6d2aad423..000000000
--- a/lib/StagehandPage.ts
+++ /dev/null
@@ -1,829 +0,0 @@
-import { Browserbase } from "@browserbasehq/sdk";
-import type { CDPSession, Page as PlaywrightPage } from "@playwright/test";
-import { chromium } from "@playwright/test";
-import { z } from "zod";
-import { Page, defaultExtractSchema } from "../types/page";
-import {
- ExtractOptions,
- ExtractResult,
- HistoryEntry,
- ObserveOptions,
- ObserveResult,
-} from "../types/stagehand";
-import { StagehandAPI } from "./api";
-import { StagehandActHandler } from "./handlers/actHandler";
-import { StagehandExtractHandler } from "./handlers/extractHandler";
-import { StagehandObserveHandler } from "./handlers/observeHandler";
-import { ActOptions, ActResult, GotoOptions, Stagehand } from "./index";
-import { LLMClient } from "./llm/LLMClient";
-import { StagehandContext } from "./StagehandContext";
-import { EnhancedContext } from "../types/context";
-import { clearOverlays } from "./utils";
-import {
- StagehandError,
- StagehandNotInitializedError,
- StagehandEnvironmentError,
- CaptchaTimeoutError,
- StagehandNotImplementedError,
- BrowserbaseSessionNotFoundError,
- MissingLLMConfigurationError,
- HandlerNotInitializedError,
- StagehandDefaultError,
-} from "../types/stagehandErrors";
-import { StagehandAPIError } from "@/types/stagehandApiErrors";
-
-export class StagehandPage {
- private stagehand: Stagehand;
- private intPage: Page;
- private intContext: StagehandContext;
- private actHandler: StagehandActHandler;
- private extractHandler: StagehandExtractHandler;
- private observeHandler: StagehandObserveHandler;
- private llmClient: LLMClient;
- private cdpClient: CDPSession | null = null;
- private api: StagehandAPI;
- private userProvidedInstructions?: string;
- private waitForCaptchaSolves: boolean;
- private initialized: boolean = false;
- private _history: Array = [];
-
- public get history(): ReadonlyArray {
- return Object.freeze([...this._history]);
- }
-
- constructor(
- page: PlaywrightPage,
- stagehand: Stagehand,
- context: StagehandContext,
- llmClient: LLMClient,
- userProvidedInstructions?: string,
- api?: StagehandAPI,
- waitForCaptchaSolves?: boolean,
- ) {
- // Create a proxy to intercept all method calls and property access
- this.intPage = new Proxy(page, {
- get: (target: PlaywrightPage, prop: keyof PlaywrightPage) => {
- // Special handling for our enhanced methods before initialization
- if (
- !this.initialized &&
- (prop === ("act" as keyof Page) ||
- prop === ("extract" as keyof Page) ||
- prop === ("observe" as keyof Page) ||
- prop === ("on" as keyof Page))
- ) {
- return () => {
- throw new StagehandNotInitializedError(String(prop));
- };
- }
-
- const value = target[prop];
- // If the property is a function, wrap it to update active page before execution
- if (typeof value === "function" && prop !== "on") {
- return (...args: unknown[]) => {
- // Update active page before executing the method
- this.intContext.setActivePage(this);
- return value.apply(target, args);
- };
- }
- return value;
- },
- }) as Page;
-
- this.stagehand = stagehand;
- this.intContext = context;
- this.llmClient = llmClient;
- this.api = api;
- this.userProvidedInstructions = userProvidedInstructions;
- this.waitForCaptchaSolves = waitForCaptchaSolves ?? false;
-
- if (this.llmClient) {
- this.actHandler = new StagehandActHandler({
- logger: this.stagehand.logger,
- stagehandPage: this,
- selfHeal: this.stagehand.selfHeal,
- });
- this.extractHandler = new StagehandExtractHandler({
- stagehand: this.stagehand,
- logger: this.stagehand.logger,
- stagehandPage: this,
- userProvidedInstructions,
- });
- this.observeHandler = new StagehandObserveHandler({
- stagehand: this.stagehand,
- logger: this.stagehand.logger,
- stagehandPage: this,
- userProvidedInstructions,
- });
- }
- }
-
- private async _refreshPageFromAPI() {
- if (!this.api) return;
-
- const sessionId = this.stagehand.browserbaseSessionID;
- if (!sessionId) {
- throw new BrowserbaseSessionNotFoundError();
- }
-
- const browserbase = new Browserbase({
- apiKey: process.env.BROWSERBASE_API_KEY,
- });
-
- const sessionStatus = await browserbase.sessions.retrieve(sessionId);
-
- const connectUrl = sessionStatus.connectUrl;
- const browser = await chromium.connectOverCDP(connectUrl);
- const context = browser.contexts()[0];
- const newPage = context.pages()[0];
-
- const newStagehandPage = await new StagehandPage(
- newPage,
- this.stagehand,
- this.intContext,
- this.llmClient,
- this.userProvidedInstructions,
- this.api,
- ).init();
-
- this.intPage = newStagehandPage.page;
-
- if (this.stagehand.debugDom) {
- this.stagehand.log({
- category: "deprecation",
- message:
- "Warning: debugDom is not supported in this version of Stagehand",
- level: 1,
- });
- }
- await this.intPage.waitForLoadState("domcontentloaded");
- await this._waitForSettledDom();
- }
-
- /**
- * Waits for a captcha to be solved when using Browserbase environment.
- *
- * @param timeoutMs - Optional timeout in milliseconds. If provided, the promise will reject if the captcha solving hasn't started within the given time.
- * @throws StagehandEnvironmentError if called in a LOCAL environment
- * @throws CaptchaTimeoutError if the timeout is reached before captcha solving starts
- * @returns Promise that resolves when the captcha is solved
- */
- public async waitForCaptchaSolve(timeoutMs?: number) {
- if (this.stagehand.env === "LOCAL") {
- throw new StagehandEnvironmentError(
- this.stagehand.env,
- "BROWSERBASE",
- "waitForCaptcha method",
- );
- }
-
- this.stagehand.log({
- category: "captcha",
- message: "Waiting for captcha",
- level: 1,
- });
-
- return new Promise((resolve, reject) => {
- let started = false;
- let timeoutId: NodeJS.Timeout;
-
- if (timeoutMs) {
- timeoutId = setTimeout(() => {
- if (!started) {
- reject(new CaptchaTimeoutError());
- }
- }, timeoutMs);
- }
-
- this.intPage.on("console", (msg) => {
- if (msg.text() === "browserbase-solving-finished") {
- this.stagehand.log({
- category: "captcha",
- message: "Captcha solving finished",
- level: 1,
- });
- if (timeoutId) clearTimeout(timeoutId);
- resolve();
- } else if (msg.text() === "browserbase-solving-started") {
- started = true;
- this.stagehand.log({
- category: "captcha",
- message: "Captcha solving started",
- level: 1,
- });
- }
- });
- });
- }
-
- async init(): Promise {
- try {
- const page = this.intPage;
- const stagehand = this.stagehand;
-
- // Create a proxy that updates active page on method calls
- const handler = {
- get: (target: PlaywrightPage, prop: string | symbol) => {
- const value = target[prop as keyof PlaywrightPage];
-
- // Handle enhanced methods
- if (prop === "act" || prop === "extract" || prop === "observe") {
- if (!this.llmClient) {
- return () => {
- throw new MissingLLMConfigurationError();
- };
- }
-
- // Use type assertion to safely call the method with proper typing
- type EnhancedMethod = (
- options:
- | ActOptions
- | ExtractOptions
- | ObserveOptions,
- ) => Promise<
- ActResult | ExtractResult | ObserveResult[]
- >;
-
- const method = this[prop as keyof StagehandPage] as EnhancedMethod;
- return async (options: unknown) => {
- this.intContext.setActivePage(this);
- return method.call(this, options);
- };
- }
-
- // Handle screenshots with CDP
- if (prop === "screenshot" && this.stagehand.env === "BROWSERBASE") {
- return async (
- options: {
- type?: "png" | "jpeg";
- quality?: number;
- fullPage?: boolean;
- clip?: { x: number; y: number; width: number; height: number };
- omitBackground?: boolean;
- } = {},
- ) => {
- const cdpOptions: Record = {
- format: options.type === "jpeg" ? "jpeg" : "png",
- quality: options.quality,
- clip: options.clip,
- omitBackground: options.omitBackground,
- fromSurface: true,
- };
-
- if (options.fullPage) {
- cdpOptions.captureBeyondViewport = true;
- }
-
- const data = await this.sendCDP<{ data: string }>(
- "Page.captureScreenshot",
- cdpOptions,
- );
-
- // Convert base64 to buffer
- const buffer = Buffer.from(data.data, "base64");
-
- return buffer;
- };
- }
-
- // Handle goto specially
- if (prop === "goto") {
- return async (url: string, options: GotoOptions) => {
- this.intContext.setActivePage(this);
- const result = this.api
- ? await this.api.goto(url, options)
- : await target.goto(url, options);
-
- this.addToHistory("navigate", { url, options }, result);
-
- if (this.waitForCaptchaSolves) {
- try {
- await this.waitForCaptchaSolve(1000);
- } catch {
- // ignore
- }
- }
-
- if (this.api) {
- await this._refreshPageFromAPI();
- } else {
- if (stagehand.debugDom) {
- this.stagehand.log({
- category: "deprecation",
- message:
- "Warning: debugDom is not supported in this version of Stagehand",
- level: 1,
- });
- }
- await target.waitForLoadState("domcontentloaded");
- await this._waitForSettledDom();
- }
- return result;
- };
- }
-
- // Handle event listeners
- if (prop === "on") {
- return (
- event: keyof PlaywrightPage["on"],
- listener: Parameters[1],
- ) => {
- if (event === "popup") {
- return this.context.on("page", async (page: PlaywrightPage) => {
- const newContext = await StagehandContext.init(
- page.context(),
- stagehand,
- );
- const newStagehandPage = new StagehandPage(
- page,
- stagehand,
- newContext,
- this.llmClient,
- );
-
- await newStagehandPage.init();
- listener(newStagehandPage.page);
- });
- }
- this.intContext.setActivePage(this);
- return target.on(event, listener);
- };
- }
-
- // For all other method calls, update active page
- if (typeof value === "function") {
- return (...args: unknown[]) => {
- this.intContext.setActivePage(this);
- return value.apply(target, args);
- };
- }
-
- return value;
- },
- };
-
- this.intPage = new Proxy(page, handler) as unknown as Page;
- this.initialized = true;
- return this;
- } catch (err: unknown) {
- if (err instanceof StagehandError || err instanceof StagehandAPIError) {
- throw err;
- }
- throw new StagehandDefaultError(err);
- }
- }
-
- public get page(): Page {
- return this.intPage;
- }
-
- public get context(): EnhancedContext {
- return this.intContext.context;
- }
-
- // We can make methods public because StagehandPage is private to the Stagehand class.
- // When a user gets stagehand.page, they are getting a proxy to the Playwright page.
- // We can override the methods on the proxy to add our own behavior
- public async _waitForSettledDom(timeoutMs?: number) {
- try {
- const timeout = timeoutMs ?? this.stagehand.domSettleTimeoutMs;
- let timeoutHandle: NodeJS.Timeout;
-
- await this.page.waitForLoadState("domcontentloaded");
-
- const timeoutPromise = new Promise((resolve) => {
- timeoutHandle = setTimeout(() => {
- this.stagehand.log({
- category: "dom",
- message: "DOM settle timeout exceeded, continuing anyway",
- level: 1,
- auxiliary: {
- timeout_ms: {
- value: timeout.toString(),
- type: "integer",
- },
- },
- });
- resolve();
- }, timeout);
- });
-
- try {
- await Promise.race([
- this.page.evaluate(() => {
- return new Promise((resolve) => {
- if (typeof window.waitForDomSettle === "function") {
- window.waitForDomSettle().then(resolve);
- } else {
- console.warn(
- "waitForDomSettle is not defined, considering DOM as settled",
- );
- resolve();
- }
- });
- }),
- this.page.waitForLoadState("domcontentloaded"),
- this.page.waitForSelector("body"),
- timeoutPromise,
- ]);
- } finally {
- clearTimeout(timeoutHandle!);
- }
- } catch (e) {
- this.stagehand.log({
- category: "dom",
- message: "Error in waitForSettledDom",
- level: 1,
- auxiliary: {
- error: {
- value: e.message,
- type: "string",
- },
- trace: {
- value: e.stack,
- type: "string",
- },
- },
- });
- }
- }
-
- public addToHistory(
- method: HistoryEntry["method"],
- parameters:
- | ActOptions
- | ExtractOptions
- | ObserveOptions
- | { url: string; options: GotoOptions }
- | string,
- result?: unknown,
- ): void {
- this._history.push({
- method,
- parameters,
- result: result ?? null,
- timestamp: new Date().toISOString(),
- });
- }
-
- async act(
- actionOrOptions: string | ActOptions | ObserveResult,
- ): Promise {
- try {
- if (!this.actHandler) {
- throw new HandlerNotInitializedError("Act");
- }
-
- await clearOverlays(this.page);
-
- // If actionOrOptions is an ObserveResult, we call actFromObserveResult.
- // We need to ensure there is both a selector and a method in the ObserveResult.
- if (typeof actionOrOptions === "object" && actionOrOptions !== null) {
- // If it has selector AND method => treat as ObserveResult
- if ("selector" in actionOrOptions && "method" in actionOrOptions) {
- const observeResult = actionOrOptions as ObserveResult;
-
- if (this.api) {
- const result = await this.api.act(observeResult);
- await this._refreshPageFromAPI();
- this.addToHistory("act", observeResult, result);
- return result;
- }
-
- // validate observeResult.method, etc.
- return this.actHandler.actFromObserveResult(observeResult);
- } else {
- // If it's an object but no selector/method,
- // check that it's truly ActOptions (i.e., has an `action` field).
- if (!("action" in actionOrOptions)) {
- throw new StagehandError(
- "Invalid argument. Valid arguments are: a string, an ActOptions object, " +
- "or an ObserveResult WITH 'selector' and 'method' fields.",
- );
- }
- }
- } else if (typeof actionOrOptions === "string") {
- // Convert string to ActOptions
- actionOrOptions = { action: actionOrOptions };
- } else {
- throw new StagehandError(
- "Invalid argument: you may have called act with an empty ObserveResult.\n" +
- "Valid arguments are: a string, an ActOptions object, or an ObserveResult " +
- "WITH 'selector' and 'method' fields.",
- );
- }
-
- const { action, modelName, modelClientOptions } = actionOrOptions;
-
- if (this.api) {
- const result = await this.api.act(actionOrOptions);
- await this._refreshPageFromAPI();
- this.addToHistory("act", actionOrOptions, result);
- return result;
- }
-
- const requestId = Math.random().toString(36).substring(2);
- const llmClient: LLMClient = modelName
- ? this.stagehand.llmProvider.getClient(modelName, modelClientOptions)
- : this.llmClient;
-
- this.stagehand.log({
- category: "act",
- message: "running act",
- level: 1,
- auxiliary: {
- action: {
- value: action,
- type: "string",
- },
- requestId: {
- value: requestId,
- type: "string",
- },
- modelName: {
- value: llmClient.modelName,
- type: "string",
- },
- },
- });
-
- const result = await this.actHandler.observeAct(
- actionOrOptions,
- this.observeHandler,
- llmClient,
- requestId,
- );
-
- this.addToHistory("act", actionOrOptions, result);
- return result;
- } catch (err: unknown) {
- if (err instanceof StagehandError || err instanceof StagehandAPIError) {
- throw err;
- }
- throw new StagehandDefaultError(err);
- }
- }
-
- async extract(
- instructionOrOptions?: string | ExtractOptions,
- ): Promise> {
- try {
- if (!this.extractHandler) {
- throw new HandlerNotInitializedError("Extract");
- }
-
- await clearOverlays(this.page);
-
- // check if user called extract() with no arguments
- if (!instructionOrOptions) {
- let result: ExtractResult;
- if (this.api) {
- result = await this.api.extract({});
- } else {
- result = await this.extractHandler.extract();
- }
- this.addToHistory("extract", instructionOrOptions, result);
- return result;
- }
-
- const options: ExtractOptions =
- typeof instructionOrOptions === "string"
- ? {
- instruction: instructionOrOptions,
- schema: defaultExtractSchema as T,
- }
- : instructionOrOptions;
-
- const {
- instruction,
- schema,
- modelName,
- modelClientOptions,
- domSettleTimeoutMs,
- useTextExtract,
- selector,
- } = options;
-
- // Throw a NotImplementedError if the user passed in an `xpath`
- // and `useTextExtract` is false
- if (selector && useTextExtract !== true) {
- throw new StagehandNotImplementedError(
- "Passing an xpath into extract is only supported when `useTextExtract: true`.",
- );
- }
-
- if (this.api) {
- const result = await this.api.extract(options);
- this.addToHistory("extract", instructionOrOptions, result);
- return result;
- }
-
- const requestId = Math.random().toString(36).substring(2);
- const llmClient = modelName
- ? this.stagehand.llmProvider.getClient(modelName, modelClientOptions)
- : this.llmClient;
-
- this.stagehand.log({
- category: "extract",
- message: "running extract",
- level: 1,
- auxiliary: {
- instruction: {
- value: instruction,
- type: "string",
- },
- requestId: {
- value: requestId,
- type: "string",
- },
- modelName: {
- value: llmClient.modelName,
- type: "string",
- },
- },
- });
-
- const result = await this.extractHandler
- .extract({
- instruction,
- schema,
- llmClient,
- requestId,
- domSettleTimeoutMs,
- useTextExtract,
- selector,
- })
- .catch((e) => {
- this.stagehand.log({
- category: "extract",
- message: "error extracting",
- level: 1,
- auxiliary: {
- error: {
- value: e.message,
- type: "string",
- },
- trace: {
- value: e.stack,
- type: "string",
- },
- },
- });
-
- if (this.stagehand.enableCaching) {
- this.stagehand.llmProvider.cleanRequestCache(requestId);
- }
-
- throw e;
- });
-
- this.addToHistory("extract", instructionOrOptions, result);
-
- return result;
- } catch (err: unknown) {
- if (err instanceof StagehandError || err instanceof StagehandAPIError) {
- throw err;
- }
- throw new StagehandDefaultError(err);
- }
- }
-
- async observe(
- instructionOrOptions?: string | ObserveOptions,
- ): Promise {
- try {
- if (!this.observeHandler) {
- throw new HandlerNotInitializedError("Observe");
- }
-
- await clearOverlays(this.page);
-
- const options: ObserveOptions =
- typeof instructionOrOptions === "string"
- ? { instruction: instructionOrOptions }
- : instructionOrOptions || {};
-
- const {
- instruction,
- modelName,
- modelClientOptions,
- domSettleTimeoutMs,
- returnAction = true,
- onlyVisible = false,
- drawOverlay,
- } = options;
-
- if (this.api) {
- const result = await this.api.observe(options);
- this.addToHistory("observe", instructionOrOptions, result);
- return result;
- }
-
- const requestId = Math.random().toString(36).substring(2);
- const llmClient = modelName
- ? this.stagehand.llmProvider.getClient(modelName, modelClientOptions)
- : this.llmClient;
-
- this.stagehand.log({
- category: "observe",
- message: "running observe",
- level: 1,
- auxiliary: {
- instruction: {
- value: instruction,
- type: "string",
- },
- requestId: {
- value: requestId,
- type: "string",
- },
- modelName: {
- value: llmClient.modelName,
- type: "string",
- },
- onlyVisible: {
- value: onlyVisible ? "true" : "false",
- type: "boolean",
- },
- },
- });
-
- const result = await this.observeHandler
- .observe({
- instruction,
- llmClient,
- requestId,
- domSettleTimeoutMs,
- returnAction,
- onlyVisible,
- drawOverlay,
- })
- .catch((e) => {
- this.stagehand.log({
- category: "observe",
- message: "error observing",
- level: 1,
- auxiliary: {
- error: {
- value: e.message,
- type: "string",
- },
- trace: {
- value: e.stack,
- type: "string",
- },
- requestId: {
- value: requestId,
- type: "string",
- },
- instruction: {
- value: instruction,
- type: "string",
- },
- },
- });
-
- if (this.stagehand.enableCaching) {
- this.stagehand.llmProvider.cleanRequestCache(requestId);
- }
-
- throw e;
- });
-
- this.addToHistory("observe", instructionOrOptions, result);
-
- return result;
- } catch (err: unknown) {
- if (err instanceof StagehandError || err instanceof StagehandAPIError) {
- throw err;
- }
- throw new StagehandDefaultError(err);
- }
- }
-
- async getCDPClient(): Promise {
- if (!this.cdpClient) {
- this.cdpClient = await this.context.newCDPSession(this.page);
- }
- return this.cdpClient;
- }
-
- async sendCDP(
- command: string,
- args?: Record,
- ): Promise {
- const client = await this.getCDPClient();
- // Type assertion needed because CDP command strings are not fully typed
- return client.send(
- command as Parameters[0],
- args || {},
- ) as Promise;
- }
-
- async enableCDP(domain: string): Promise {
- await this.sendCDP(`${domain}.enable`, {});
- }
-
- async disableCDP(domain: string): Promise {
- await this.sendCDP(`${domain}.disable`, {});
- }
-}
diff --git a/lib/a11y/utils.ts b/lib/a11y/utils.ts
deleted file mode 100644
index f19ba765a..000000000
--- a/lib/a11y/utils.ts
+++ /dev/null
@@ -1,825 +0,0 @@
-import { AccessibilityNode, TreeResult, AXNode } from "../../types/context";
-import { StagehandPage } from "../StagehandPage";
-import { LogLine } from "../../types/log";
-import { CDPSession, Page, Locator } from "playwright";
-import {
- PlaywrightCommandMethodNotSupportedException,
- PlaywrightCommandException,
-} from "@/types/playwright";
-
-// Parser function for str output
-export function formatSimplifiedTree(
- node: AccessibilityNode,
- level = 0,
-): string {
- const indent = " ".repeat(level);
- let result = `${indent}[${node.nodeId}] ${node.role}${
- node.name ? `: ${node.name}` : ""
- }\n`;
-
- if (node.children?.length) {
- result += node.children
- .map((child) => formatSimplifiedTree(child, level + 1))
- .join("");
- }
- return result;
-}
-
-/**
- * Helper function to remove or collapse unnecessary structural nodes
- * Handles three cases:
- * 1. Removes generic/none nodes with no children
- * 2. Collapses generic/none nodes with single child
- * 3. Keeps generic/none nodes with multiple children but cleans their subtrees
- * and attempts to resolve their role to a DOM tag name
- */
-async function cleanStructuralNodes(
- node: AccessibilityNode,
- page?: StagehandPage,
- logger?: (logLine: LogLine) => void,
-): Promise {
- // 1) Filter out nodes with negative IDs
- if (node.nodeId && parseInt(node.nodeId) < 0) {
- return null;
- }
-
- // 2) Base case: if no children exist, this is effectively a leaf.
- // If it's "generic" or "none", we remove it; otherwise, keep it.
- if (!node.children || node.children.length === 0) {
- return node.role === "generic" || node.role === "none" ? null : node;
- }
-
- // 3) Recursively clean children
- const cleanedChildrenPromises = node.children.map((child) =>
- cleanStructuralNodes(child, page, logger),
- );
- const resolvedChildren = await Promise.all(cleanedChildrenPromises);
- let cleanedChildren = resolvedChildren.filter(
- (child): child is AccessibilityNode => child !== null,
- );
-
- // 4) **Prune** "generic" or "none" nodes first,
- // before resolving them to their tag names.
- if (node.role === "generic" || node.role === "none") {
- if (cleanedChildren.length === 1) {
- // Collapse single-child structural node
- return cleanedChildren[0];
- } else if (cleanedChildren.length === 0) {
- // Remove empty structural node
- return null;
- }
- // If we have multiple children, we keep this node as a container.
- // We'll update role below if needed.
- }
-
- // 5) If we still have a "generic"/"none" node after pruning
- // (i.e., because it had multiple children), now we try
- // to resolve and replace its role with the DOM tag name.
- if (
- page &&
- logger &&
- node.backendDOMNodeId !== undefined &&
- (node.role === "generic" || node.role === "none")
- ) {
- try {
- const { object } = await page.sendCDP<{
- object: { objectId?: string };
- }>("DOM.resolveNode", {
- backendNodeId: node.backendDOMNodeId,
- });
-
- if (object && object.objectId) {
- try {
- // Get the tagName for the node
- const { result } = await page.sendCDP<{
- result: { type: string; value?: string };
- }>("Runtime.callFunctionOn", {
- objectId: object.objectId,
- functionDeclaration: `
- function() {
- return this.tagName ? this.tagName.toLowerCase() : "";
- }
- `,
- returnByValue: true,
- });
-
- // If we got a tagName, update the node's role
- if (result?.value) {
- node.role = result.value;
- }
- } catch (tagNameError) {
- logger({
- category: "observation",
- message: `Could not fetch tagName for node ${node.backendDOMNodeId}`,
- level: 2,
- auxiliary: {
- error: {
- value: tagNameError.message,
- type: "string",
- },
- },
- });
- }
- }
- } catch (resolveError) {
- logger({
- category: "observation",
- message: `Could not resolve DOM node ID ${node.backendDOMNodeId}`,
- level: 2,
- auxiliary: {
- error: {
- value: resolveError.message,
- type: "string",
- },
- },
- });
- }
- }
-
- // rm redundant StaticText children
- cleanedChildren = removeRedundantStaticTextChildren(node, cleanedChildren);
-
- if (cleanedChildren.length === 0) {
- if (node.role === "generic" || node.role === "none") {
- return null;
- } else {
- return { ...node, children: [] };
- }
- }
-
- // 6) Return the updated node.
- // If it has children, update them; otherwise keep it as-is.
- return cleanedChildren.length > 0
- ? { ...node, children: cleanedChildren }
- : node;
-}
-
-/**
- * Builds a hierarchical tree structure from a flat array of accessibility nodes.
- * The function processes nodes in multiple passes to create a clean, meaningful tree.
- * @param nodes - Flat array of accessibility nodes from the CDP
- * @returns Object containing both the tree structure and a simplified string representation
- */
-export async function buildHierarchicalTree(
- nodes: AccessibilityNode[],
- page?: StagehandPage,
- logger?: (logLine: LogLine) => void,
-): Promise {
- // Map to store nodeId -> URL for only those nodes that do have a URL.
- const idToUrl: Record = {};
-
- // Map to store processed nodes for quick lookup
- const nodeMap = new Map();
- const iframe_list: AccessibilityNode[] = [];
-
- // First pass: Create nodes that are meaningful
- // We only keep nodes that either have a name or children to avoid cluttering the tree
- nodes.forEach((node) => {
- // Skip node if its ID is negative (e.g., "-1000002014")
- const nodeIdValue = parseInt(node.nodeId, 10);
- if (nodeIdValue < 0) {
- return;
- }
-
- const url = extractUrlFromAXNode(node);
- if (url) {
- idToUrl[node.nodeId] = url;
- }
-
- const hasChildren = node.childIds && node.childIds.length > 0;
- const hasValidName = node.name && node.name.trim() !== "";
- const isInteractive =
- node.role !== "none" &&
- node.role !== "generic" &&
- node.role !== "InlineTextBox"; //add other interactive roles here
-
- // Include nodes that are either named, have children, or are interactive
- if (!hasValidName && !hasChildren && !isInteractive) {
- return;
- }
-
- // Create a clean node object with only relevant properties
- nodeMap.set(node.nodeId, {
- role: node.role,
- nodeId: node.nodeId,
- ...(hasValidName && { name: node.name }), // Only include name if it exists and isn't empty
- ...(node.description && { description: node.description }),
- ...(node.value && { value: node.value }),
- ...(node.backendDOMNodeId !== undefined && {
- backendDOMNodeId: node.backendDOMNodeId,
- }),
- });
- });
-
- // Second pass: Establish parent-child relationships
- // This creates the actual tree structure by connecting nodes based on parentId
- nodes.forEach((node) => {
- // Add iframes to a list and include in the return object
- const isIframe = node.role === "Iframe";
- if (isIframe) {
- const iframeNode = {
- role: node.role,
- nodeId: node.nodeId,
- };
- iframe_list.push(iframeNode);
- }
- if (node.parentId && nodeMap.has(node.nodeId)) {
- const parentNode = nodeMap.get(node.parentId);
- const currentNode = nodeMap.get(node.nodeId);
-
- if (parentNode && currentNode) {
- if (!parentNode.children) {
- parentNode.children = [];
- }
- parentNode.children.push(currentNode);
- }
- }
- });
-
- // Final pass: Build the root-level tree and clean up structural nodes
- const rootNodes = nodes
- .filter((node) => !node.parentId && nodeMap.has(node.nodeId)) // Get root nodes
- .map((node) => nodeMap.get(node.nodeId))
- .filter(Boolean) as AccessibilityNode[];
-
- const cleanedTreePromises = rootNodes.map((node) =>
- cleanStructuralNodes(node, page, logger),
- );
- const finalTree = (await Promise.all(cleanedTreePromises)).filter(
- Boolean,
- ) as AccessibilityNode[];
-
- // Generate a simplified string representation of the tree
- const simplifiedFormat = finalTree
- .map((node) => formatSimplifiedTree(node))
- .join("\n");
-
- return {
- tree: finalTree,
- simplified: simplifiedFormat,
- iframes: iframe_list,
- idToUrl: idToUrl,
- };
-}
-
-/**
- * Retrieves the full accessibility tree via CDP and transforms it into a hierarchical structure.
- */
-export async function getAccessibilityTree(
- page: StagehandPage,
- logger: (logLine: LogLine) => void,
-): Promise {
- await page.enableCDP("Accessibility");
-
- try {
- // Identify which elements are scrollable and get their backendNodeIds
- const scrollableBackendIds = await findScrollableElementIds(page);
-
- // Fetch the full accessibility tree from Chrome DevTools Protocol
- const { nodes } = await page.sendCDP<{ nodes: AXNode[] }>(
- "Accessibility.getFullAXTree",
- );
- const startTime = Date.now();
-
- // Transform into hierarchical structure
- const hierarchicalTree = await buildHierarchicalTree(
- nodes.map((node) => {
- let roleValue = node.role?.value || "";
-
- if (scrollableBackendIds.has(node.backendDOMNodeId)) {
- if (roleValue === "generic" || roleValue === "none") {
- roleValue = "scrollable";
- } else {
- roleValue = roleValue ? `scrollable, ${roleValue}` : "scrollable";
- }
- }
-
- return {
- role: roleValue,
- name: node.name?.value,
- description: node.description?.value,
- value: node.value?.value,
- nodeId: node.nodeId,
- backendDOMNodeId: node.backendDOMNodeId,
- parentId: node.parentId,
- childIds: node.childIds,
- properties: node.properties,
- };
- }),
- page,
- logger,
- );
-
- logger({
- category: "observation",
- message: `got accessibility tree in ${Date.now() - startTime}ms`,
- level: 1,
- });
- return hierarchicalTree;
- } catch (error) {
- logger({
- category: "observation",
- message: "Error getting accessibility tree",
- level: 1,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
- throw error;
- } finally {
- await page.disableCDP("Accessibility");
- }
-}
-
-// This function is wrapped into a string and sent as a CDP command
-// It is not meant to be actually executed here
-const functionString = `
-function getNodePath(el) {
- if (!el || (el.nodeType !== Node.ELEMENT_NODE && el.nodeType !== Node.TEXT_NODE)) {
- console.log("el is not a valid node type");
- return "";
- }
-
- const parts = [];
- let current = el;
-
- while (current && (current.nodeType === Node.ELEMENT_NODE || current.nodeType === Node.TEXT_NODE)) {
- let index = 0;
- let hasSameTypeSiblings = false;
- const siblings = current.parentElement
- ? Array.from(current.parentElement.childNodes)
- : [];
-
- for (let i = 0; i < siblings.length; i++) {
- const sibling = siblings[i];
- if (
- sibling.nodeType === current.nodeType &&
- sibling.nodeName === current.nodeName
- ) {
- index = index + 1;
- hasSameTypeSiblings = true;
- if (sibling.isSameNode(current)) {
- break;
- }
- }
- }
-
- if (!current || !current.parentNode) break;
- if (current.nodeName.toLowerCase() === "html"){
- parts.unshift("html");
- break;
- }
-
- // text nodes are handled differently in XPath
- if (current.nodeName !== "#text") {
- const tagName = current.nodeName.toLowerCase();
- const pathIndex = hasSameTypeSiblings ? \`[\${index}]\` : "";
- parts.unshift(\`\${tagName}\${pathIndex}\`);
- }
-
- current = current.parentElement;
- }
-
- return parts.length ? \`/\${parts.join("/")}\` : "";
-}`;
-
-export async function getXPathByResolvedObjectId(
- cdpClient: CDPSession,
- resolvedObjectId: string,
-): Promise {
- const { result } = await cdpClient.send("Runtime.callFunctionOn", {
- objectId: resolvedObjectId,
- functionDeclaration: `function() {
- ${functionString}
- return getNodePath(this);
- }`,
- returnByValue: true,
- });
-
- return result.value || "";
-}
-
-/**
- * `findScrollableElementIds` is a function that identifies elements in
- * the browser that are deemed "scrollable". At a high level, it does the
- * following:
- * - Calls the browser-side `window.getScrollableElementXpaths()` function,
- * which returns a list of XPaths for scrollable containers.
- * - Iterates over the returned list of XPaths, locating each element in the DOM
- * using `stagehandPage.sendCDP(...)`
- * - During each iteration, we call `Runtime.evaluate` to run `document.evaluate(...)`
- * with each XPath, obtaining a `RemoteObject` reference if it exists.
- * - Then, for each valid object reference, we call `DOM.describeNode` to retrieve
- * the element’s `backendNodeId`.
- * - Collects all resulting `backendNodeId`s in a Set and returns them.
- *
- * @param stagehandPage - A StagehandPage instance with built-in CDP helpers.
- * @returns A Promise that resolves to a Set of unique `backendNodeId`s corresponding
- * to scrollable elements in the DOM.
- */
-export async function findScrollableElementIds(
- stagehandPage: StagehandPage,
-): Promise> {
- // get the xpaths of the scrollable elements
- const xpaths = await stagehandPage.page.evaluate(() => {
- return window.getScrollableElementXpaths();
- });
-
- const scrollableBackendIds = new Set();
-
- for (const xpath of xpaths) {
- if (!xpath) continue;
-
- // evaluate the XPath in the stagehandPage
- const { result } = await stagehandPage.sendCDP<{
- result?: { objectId?: string };
- }>("Runtime.evaluate", {
- expression: `
- (function() {
- const res = document.evaluate(${JSON.stringify(
- xpath,
- )}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
- return res.singleNodeValue;
- })();
- `,
- returnByValue: false,
- });
-
- // if we have an objectId, call DOM.describeNode to get backendNodeId
- if (result?.objectId) {
- const { node } = await stagehandPage.sendCDP<{
- node?: { backendNodeId?: number };
- }>("DOM.describeNode", {
- objectId: result.objectId,
- });
-
- if (node?.backendNodeId) {
- scrollableBackendIds.add(node.backendNodeId);
- }
- }
- }
-
- return scrollableBackendIds;
-}
-
-/**
- * Removes any StaticText children whose combined text equals the parent's name.
- * This is most often used to avoid duplicating a link's accessible name in separate child nodes.
- *
- * @param parent The parent accessibility node whose `.name` we check.
- * @param children The parent's current children list, typically after cleaning.
- * @returns A filtered list of children with redundant StaticText nodes removed.
- */
-function removeRedundantStaticTextChildren(
- parent: AccessibilityNode,
- children: AccessibilityNode[],
-): AccessibilityNode[] {
- if (!parent.name) {
- return children;
- }
-
- const parentName = parent.name.replace(/\s+/g, " ").trim();
-
- // Gather all StaticText children and combine their text
- const staticTextChildren = children.filter(
- (child) => child.role === "StaticText" && child.name,
- );
- const combinedChildText = staticTextChildren
- .map((child) => child.name!.replace(/\s+/g, " ").trim())
- .join("");
-
- // If the combined text exactly matches the parent's name, remove those child nodes
- if (combinedChildText === parentName) {
- return children.filter((child) => child.role !== "StaticText");
- }
-
- return children;
-}
-
-function extractUrlFromAXNode(axNode: AccessibilityNode): string | undefined {
- if (!axNode.properties) return undefined;
- const urlProp = axNode.properties.find((prop) => prop.name === "url");
- if (urlProp && urlProp.value && typeof urlProp.value.value === "string") {
- return urlProp.value.value.trim();
- }
- return undefined;
-}
-
-export async function performPlaywrightMethod(
- stagehandPage: Page,
- logger: (logLine: LogLine) => void,
- method: string,
- args: unknown[],
- xpath: string,
-) {
- const locator = stagehandPage.locator(`xpath=${xpath}`).first();
- const initialUrl = stagehandPage.url();
-
- logger({
- category: "action",
- message: "performing playwright method",
- level: 2,
- auxiliary: {
- xpath: {
- value: xpath,
- type: "string",
- },
- method: {
- value: method,
- type: "string",
- },
- },
- });
-
- if (method === "scrollIntoView") {
- logger({
- category: "action",
- message: "scrolling element into view",
- level: 2,
- auxiliary: {
- xpath: {
- value: xpath,
- type: "string",
- },
- },
- });
- try {
- await locator
- .evaluate((element: HTMLElement) => {
- element.scrollIntoView({ behavior: "smooth", block: "center" });
- })
- .catch((e: Error) => {
- logger({
- category: "action",
- message: "error scrolling element into view",
- level: 1,
- auxiliary: {
- error: {
- value: e.message,
- type: "string",
- },
- trace: {
- value: e.stack,
- type: "string",
- },
- xpath: {
- value: xpath,
- type: "string",
- },
- },
- });
- });
- } catch (e) {
- logger({
- category: "action",
- message: "error scrolling element into view",
- level: 1,
- auxiliary: {
- error: {
- value: e.message,
- type: "string",
- },
- trace: {
- value: e.stack,
- type: "string",
- },
- xpath: {
- value: xpath,
- type: "string",
- },
- },
- });
-
- throw new PlaywrightCommandException(e.message);
- }
- } else if (method === "fill" || method === "type") {
- try {
- await locator.fill("");
- await locator.click();
- const text = args[0]?.toString();
- for (const char of text) {
- await stagehandPage.keyboard.type(char, {
- delay: Math.random() * 50 + 25,
- });
- }
- } catch (e) {
- logger({
- category: "action",
- message: "error filling element",
- level: 1,
- auxiliary: {
- error: {
- value: e.message,
- type: "string",
- },
- trace: {
- value: e.stack,
- type: "string",
- },
- xpath: {
- value: xpath,
- type: "string",
- },
- },
- });
-
- throw new PlaywrightCommandException(e.message);
- }
- } else if (method === "press") {
- try {
- const key = args[0]?.toString();
- await stagehandPage.keyboard.press(key);
- } catch (e) {
- logger({
- category: "action",
- message: "error pressing key",
- level: 1,
- auxiliary: {
- error: {
- value: e.message,
- type: "string",
- },
- trace: {
- value: e.stack,
- type: "string",
- },
- key: {
- value: args[0]?.toString() ?? "unknown",
- type: "string",
- },
- },
- });
-
- throw new PlaywrightCommandException(e.message);
- }
- } else if (typeof locator[method as keyof typeof locator] === "function") {
- // Log current URL before action
- logger({
- category: "action",
- message: "page URL before action",
- level: 2,
- auxiliary: {
- url: {
- value: stagehandPage.url(),
- type: "string",
- },
- },
- });
-
- // Perform the action
- try {
- await (
- locator[method as keyof Locator] as unknown as (
- ...args: string[]
- ) => Promise
- )(...args.map((arg) => arg?.toString() || ""));
- } catch (e) {
- logger({
- category: "action",
- message: "error performing method",
- level: 1,
- auxiliary: {
- error: {
- value: e.message,
- type: "string",
- },
- trace: {
- value: e.stack,
- type: "string",
- },
- xpath: {
- value: xpath,
- type: "string",
- },
- method: {
- value: method,
- type: "string",
- },
- args: {
- value: JSON.stringify(args),
- type: "object",
- },
- },
- });
-
- throw new PlaywrightCommandException(e.message);
- }
-
- // Handle navigation if a new page is opened
- if (method === "click") {
- logger({
- category: "action",
- message: "clicking element, checking for page navigation",
- level: 1,
- auxiliary: {
- xpath: {
- value: xpath,
- type: "string",
- },
- },
- });
-
- const newOpenedTab = await Promise.race([
- new Promise((resolve) => {
- Promise.resolve(stagehandPage.context()).then((context) => {
- context.once("page", (page: Page) => resolve(page));
- setTimeout(() => resolve(null), 1_500);
- });
- }),
- ]);
-
- logger({
- category: "action",
- message: "clicked element",
- level: 1,
- auxiliary: {
- newOpenedTab: {
- value: newOpenedTab ? "opened a new tab" : "no new tabs opened",
- type: "string",
- },
- },
- });
-
- if (newOpenedTab) {
- logger({
- category: "action",
- message: "new page detected (new tab) with URL",
- level: 1,
- auxiliary: {
- url: {
- value: newOpenedTab.url(),
- type: "string",
- },
- },
- });
- await newOpenedTab.close();
- await stagehandPage.goto(newOpenedTab.url());
- await stagehandPage.waitForLoadState("domcontentloaded");
- }
-
- await Promise.race([
- stagehandPage.waitForLoadState("networkidle"),
- new Promise((resolve) => setTimeout(resolve, 5_000)),
- ]).catch((e) => {
- logger({
- category: "action",
- message: "network idle timeout hit",
- level: 1,
- auxiliary: {
- trace: {
- value: e.stack,
- type: "string",
- },
- message: {
- value: e.message,
- type: "string",
- },
- },
- });
- });
-
- logger({
- category: "action",
- message: "finished waiting for (possible) page navigation",
- level: 1,
- });
-
- if (stagehandPage.url() !== initialUrl) {
- logger({
- category: "action",
- message: "new page detected with URL",
- level: 1,
- auxiliary: {
- url: {
- value: stagehandPage.url(),
- type: "string",
- },
- },
- });
- }
- }
- } else {
- logger({
- category: "action",
- message: "chosen method is invalid",
- level: 1,
- auxiliary: {
- method: {
- value: method,
- type: "string",
- },
- },
- });
-
- throw new PlaywrightCommandMethodNotSupportedException(
- `Method ${method} not supported`,
- );
- }
-}
diff --git a/lib/agent/AgentProvider.ts b/lib/agent/AgentProvider.ts
deleted file mode 100644
index cc8110fa4..000000000
--- a/lib/agent/AgentProvider.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { LogLine } from "@/types/log";
-import { AgentClient } from "./AgentClient";
-import { AgentType } from "@/types/agent";
-import { OpenAICUAClient } from "./OpenAICUAClient";
-import { AnthropicCUAClient } from "./AnthropicCUAClient";
-import {
- UnsupportedModelError,
- UnsupportedModelProviderError,
-} from "@/types/stagehandErrors";
-
-// Map model names to their provider types
-const modelToAgentProviderMap: Record = {
- "computer-use-preview": "openai",
- "claude-3-5-sonnet-20240620": "anthropic",
- "claude-3-7-sonnet-20250219": "anthropic", // Add newer Claude models
-};
-
-/**
- * Provider for agent clients
- * This class is responsible for creating the appropriate agent client
- * based on the provider type
- */
-export class AgentProvider {
- private logger: (message: LogLine) => void;
-
- /**
- * Create a new agent provider
- */
- constructor(logger: (message: LogLine) => void) {
- this.logger = logger;
- }
-
- getClient(
- modelName: string,
- clientOptions?: Record,
- userProvidedInstructions?: string,
- ): AgentClient {
- const type = AgentProvider.getAgentProvider(modelName);
- this.logger({
- category: "agent",
- message: `Getting agent client for type: ${type}, model: ${modelName}`,
- level: 2,
- });
-
- try {
- switch (type) {
- case "openai":
- return new OpenAICUAClient(
- type,
- modelName,
- userProvidedInstructions,
- clientOptions,
- );
- case "anthropic":
- return new AnthropicCUAClient(
- type,
- modelName,
- userProvidedInstructions,
- clientOptions,
- );
- default:
- throw new UnsupportedModelProviderError(
- ["openai", "anthropic"],
- "Computer Use Agent",
- );
- }
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : String(error);
- this.logger({
- category: "agent",
- message: `Error creating agent client: ${errorMessage}`,
- level: 0,
- });
- throw error;
- }
- }
-
- static getAgentProvider(modelName: string): AgentType {
- // First check the exact model name in the map
- if (modelName in modelToAgentProviderMap) {
- return modelToAgentProviderMap[modelName];
- }
-
- throw new UnsupportedModelError(
- Object.keys(modelToAgentProviderMap),
- "Computer Use Agent",
- );
- }
-}
diff --git a/lib/agent/StagehandAgent.ts b/lib/agent/StagehandAgent.ts
deleted file mode 100644
index ab65d1efb..000000000
--- a/lib/agent/StagehandAgent.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { LogLine } from "@/types/log";
-import {
- AgentExecuteOptions,
- AgentResult,
- AgentExecutionOptions,
-} from "@/types/agent";
-import { AgentClient } from "./AgentClient";
-
-/**
- * Main interface for agent operations in Stagehand
- * This class provides methods for executing tasks with an agent
- */
-export class StagehandAgent {
- private client: AgentClient;
- private logger: (message: LogLine) => void;
-
- constructor(client: AgentClient, logger: (message: LogLine) => void) {
- this.client = client;
- this.logger = logger;
- }
-
- async execute(
- optionsOrInstruction: AgentExecuteOptions | string,
- ): Promise {
- const options =
- typeof optionsOrInstruction === "string"
- ? { instruction: optionsOrInstruction }
- : optionsOrInstruction;
-
- this.logger({
- category: "agent",
- message: `Executing agent task: ${options.instruction}`,
- level: 1,
- });
-
- const executionOptions: AgentExecutionOptions = {
- options,
- logger: this.logger,
- retries: 3,
- };
-
- return await this.client.execute(executionOptions);
- }
-
- getModelName(): string {
- return this.client.modelName;
- }
-
- getAgentType(): string {
- return this.client.type;
- }
-}
diff --git a/lib/api.ts b/lib/api.ts
deleted file mode 100644
index e05b96aad..000000000
--- a/lib/api.ts
+++ /dev/null
@@ -1,247 +0,0 @@
-import { z } from "zod";
-import zodToJsonSchema from "zod-to-json-schema";
-import {
- ApiResponse,
- ExecuteActionParams,
- StagehandAPIConstructorParams,
- StartSessionParams,
- StartSessionResult,
-} from "../types/api";
-import { LogLine } from "../types/log";
-import { GotoOptions } from "../types/playwright";
-import {
- ActOptions,
- ActResult,
- AgentConfig,
- ExtractOptions,
- ExtractResult,
- ObserveOptions,
- ObserveResult,
-} from "../types/stagehand";
-import { AgentExecuteOptions, AgentResult } from ".";
-import {
- StagehandAPIUnauthorizedError,
- StagehandHttpError,
- StagehandAPIError,
- StagehandServerError,
- StagehandResponseBodyError,
- StagehandResponseParseError,
-} from "../types/stagehandApiErrors";
-
-export class StagehandAPI {
- private apiKey: string;
- private projectId: string;
- private sessionId?: string;
- private modelApiKey: string;
- private logger: (message: LogLine) => void;
-
- constructor({ apiKey, projectId, logger }: StagehandAPIConstructorParams) {
- this.apiKey = apiKey;
- this.projectId = projectId;
- this.logger = logger;
- }
-
- async init({
- modelName,
- modelApiKey,
- domSettleTimeoutMs,
- verbose,
- debugDom,
- systemPrompt,
- selfHeal,
- waitForCaptchaSolves,
- actionTimeoutMs,
- browserbaseSessionCreateParams,
- browserbaseSessionID,
- }: StartSessionParams): Promise {
- if (!modelApiKey) {
- throw new StagehandAPIError("modelApiKey is required");
- }
- this.modelApiKey = modelApiKey;
- const sessionResponse = await this.request("/sessions/start", {
- method: "POST",
- body: JSON.stringify({
- modelName,
- domSettleTimeoutMs,
- verbose,
- debugDom,
- systemPrompt,
- selfHeal,
- waitForCaptchaSolves,
- actionTimeoutMs,
- browserbaseSessionCreateParams,
- browserbaseSessionID,
- }),
- });
-
- if (sessionResponse.status === 401) {
- throw new StagehandAPIUnauthorizedError(
- "Unauthorized. Ensure you provided a valid API key and that it is whitelisted.",
- );
- } else if (sessionResponse.status !== 200) {
- console.log(await sessionResponse.text());
- throw new StagehandHttpError(`Unknown error: ${sessionResponse.status}`);
- }
-
- const sessionResponseBody =
- (await sessionResponse.json()) as ApiResponse;
-
- if (sessionResponseBody.success === false) {
- throw new StagehandAPIError(sessionResponseBody.message);
- }
-
- this.sessionId = sessionResponseBody.data.sessionId;
-
- return sessionResponseBody.data;
- }
-
- async act(options: ActOptions | ObserveResult): Promise {
- return this.execute({
- method: "act",
- args: { ...options },
- });
- }
-
- async extract(
- options: ExtractOptions,
- ): Promise> {
- if (!options.schema) {
- return this.execute>({
- method: "extract",
- args: {},
- });
- }
- const parsedSchema = zodToJsonSchema(options.schema);
- return this.execute>({
- method: "extract",
- args: { ...options, schemaDefinition: parsedSchema },
- });
- }
-
- async observe(options?: ObserveOptions): Promise {
- return this.execute({
- method: "observe",
- args: { ...options },
- });
- }
-
- async goto(url: string, options?: GotoOptions): Promise {
- return this.execute({
- method: "navigate",
- args: { url, options },
- });
- }
-
- async agentExecute(
- agentConfig: AgentConfig,
- executeOptions: AgentExecuteOptions,
- ): Promise {
- return this.execute({
- method: "agentExecute",
- args: { agentConfig, executeOptions },
- });
- }
-
- async end(): Promise {
- const url = `/sessions/${this.sessionId}/end`;
- return await this.request(url, {
- method: "POST",
- });
- }
-
- private async execute({
- method,
- args,
- params,
- }: ExecuteActionParams): Promise {
- const urlParams = new URLSearchParams(params as Record);
- const queryString = urlParams.toString();
- const url = `/sessions/${this.sessionId}/${method}${queryString ? `?${queryString}` : ""}`;
-
- const response = await this.request(url, {
- method: "POST",
- body: JSON.stringify(args),
- });
-
- if (!response.ok) {
- const errorBody = await response.text();
- throw new StagehandHttpError(
- `HTTP error! status: ${response.status}, body: ${errorBody}`,
- );
- }
-
- if (!response.body) {
- throw new StagehandResponseBodyError();
- }
-
- const reader = response.body.getReader();
- const decoder = new TextDecoder();
- let buffer = "";
-
- while (true) {
- const { value, done } = await reader.read();
-
- if (done && !buffer) {
- return null;
- }
-
- buffer += decoder.decode(value, { stream: true });
- const lines = buffer.split("\n\n");
- buffer = lines.pop() || "";
-
- for (const line of lines) {
- if (!line.startsWith("data: ")) continue;
-
- try {
- const eventData = JSON.parse(line.slice(6));
-
- if (eventData.type === "system") {
- if (eventData.data.status === "error") {
- throw new StagehandServerError(eventData.data.error);
- }
- if (eventData.data.status === "finished") {
- return eventData.data.result as T;
- }
- } else if (eventData.type === "log") {
- this.logger(eventData.data.message);
- }
- } catch (e) {
- console.error("Error parsing event data:", e);
- throw new StagehandResponseParseError(
- "Failed to parse server response",
- );
- }
- }
-
- if (done) break;
- }
- }
-
- private async request(
- path: string,
- options: RequestInit = {},
- ): Promise {
- const defaultHeaders: Record = {
- "x-bb-api-key": this.apiKey,
- "x-bb-project-id": this.projectId,
- "x-bb-session-id": this.sessionId,
- // we want real-time logs, so we stream the response
- "x-stream-response": "true",
- "x-model-api-key": this.modelApiKey,
- };
-
- if (options.method === "POST" && options.body) {
- defaultHeaders["Content-Type"] = "application/json";
- }
-
- const response = await fetch(`${process.env.STAGEHAND_API_URL}${path}`, {
- ...options,
- headers: {
- ...defaultHeaders,
- ...options.headers,
- },
- });
-
- return response;
- }
-}
diff --git a/lib/cache.ts b/lib/cache.ts
deleted file mode 100644
index a9e2a981d..000000000
--- a/lib/cache.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import fs from "fs";
-const observationsPath = "./.cache/observations.json";
-const actionsPath = "./.cache/actions.json";
-
-/**
- * A file system cache to skip inference when repeating steps
- * It also acts as the source of truth for identifying previously seen actions and observations
- */
-class Cache {
- disabled: boolean;
-
- constructor({ disabled = false } = {}) {
- this.disabled = disabled;
- if (!this.disabled) {
- this.initCache();
- }
- }
-
- readObservations() {
- if (this.disabled) {
- return {};
- }
- try {
- return JSON.parse(fs.readFileSync(observationsPath, "utf8"));
- } catch (error) {
- console.error("Error reading from observations.json", error);
- return {};
- }
- }
-
- readActions() {
- if (this.disabled) {
- return {};
- }
- try {
- return JSON.parse(fs.readFileSync(actionsPath, "utf8"));
- } catch (error) {
- console.error("Error reading from actions.json", error);
- return {};
- }
- }
-
- writeObservations({
- key,
- value,
- }: {
- key: string;
- value: { id: string; result: string };
- }) {
- if (this.disabled) {
- return;
- }
-
- const observations = this.readObservations();
- observations[key] = value;
- fs.writeFileSync(observationsPath, JSON.stringify(observations, null, 2));
- }
-
- writeActions({
- key,
- value,
- }: {
- key: string;
- value: { id: string; result: string };
- }) {
- if (this.disabled) {
- return;
- }
-
- const actions = this.readActions();
- actions[key] = value;
- fs.writeFileSync(actionsPath, JSON.stringify(actions, null, 2));
- }
-
- evictCache() {
- throw new Error("implement me");
- }
-
- private initCache() {
- if (this.disabled) {
- return;
- }
- const cacheDir = ".cache";
-
- if (!fs.existsSync(cacheDir)) {
- fs.mkdirSync(cacheDir);
- }
- if (!fs.existsSync(actionsPath)) {
- fs.writeFileSync(actionsPath, JSON.stringify({}));
- }
-
- if (!fs.existsSync(observationsPath)) {
- fs.writeFileSync(observationsPath, JSON.stringify({}));
- }
- }
-}
-
-export default Cache;
diff --git a/lib/cache/ActionCache.ts b/lib/cache/ActionCache.ts
deleted file mode 100644
index b54801d6c..000000000
--- a/lib/cache/ActionCache.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { LogLine } from "../../types/log";
-import { BaseCache, CacheEntry } from "./BaseCache";
-
-export interface PlaywrightCommand {
- method: string;
- args: string[];
-}
-
-export interface ActionEntry extends CacheEntry {
- data: {
- playwrightCommand: PlaywrightCommand;
- componentString: string;
- xpaths: string[];
- newStepString: string;
- completed: boolean;
- previousSelectors: string[];
- action: string;
- };
-}
-
-/**
- * ActionCache handles logging and retrieving actions along with their Playwright commands.
- */
-export class ActionCache extends BaseCache {
- constructor(
- logger: (message: LogLine) => void,
- cacheDir?: string,
- cacheFile?: string,
- ) {
- super(logger, cacheDir, cacheFile || "action_cache.json");
- }
-
- public async addActionStep({
- url,
- action,
- previousSelectors,
- playwrightCommand,
- componentString,
- xpaths,
- newStepString,
- completed,
- requestId,
- }: {
- url: string;
- action: string;
- previousSelectors: string[];
- playwrightCommand: PlaywrightCommand;
- componentString: string;
- requestId: string;
- xpaths: string[];
- newStepString: string;
- completed: boolean;
- }): Promise {
- this.logger({
- category: "action_cache",
- message: "adding action step to cache",
- level: 1,
- auxiliary: {
- action: {
- value: action,
- type: "string",
- },
- requestId: {
- value: requestId,
- type: "string",
- },
- url: {
- value: url,
- type: "string",
- },
- previousSelectors: {
- value: JSON.stringify(previousSelectors),
- type: "object",
- },
- },
- });
-
- await this.set(
- { url, action, previousSelectors },
- {
- playwrightCommand,
- componentString,
- xpaths,
- newStepString,
- completed,
- previousSelectors,
- action,
- },
- requestId,
- );
- }
-
- /**
- * Retrieves all actions for a specific trajectory.
- * @param trajectoryId - Unique identifier for the trajectory.
- * @param requestId - The identifier for the current request.
- * @returns An array of TrajectoryEntry objects or null if not found.
- */
- public async getActionStep({
- url,
- action,
- previousSelectors,
- requestId,
- }: {
- url: string;
- action: string;
- previousSelectors: string[];
- requestId: string;
- }): Promise {
- const data = await super.get({ url, action, previousSelectors }, requestId);
- if (!data) {
- return null;
- }
-
- return data;
- }
-
- public async removeActionStep(cacheHashObj: {
- url: string;
- action: string;
- previousSelectors: string[];
- requestId: string;
- }): Promise {
- await super.delete(cacheHashObj);
- }
-
- /**
- * Clears all actions for a specific trajectory.
- * @param trajectoryId - Unique identifier for the trajectory.
- * @param requestId - The identifier for the current request.
- */
- public async clearAction(requestId: string): Promise {
- await super.deleteCacheForRequestId(requestId);
- this.logger({
- category: "action_cache",
- message: "cleared action for ID",
- level: 1,
- auxiliary: {
- requestId: {
- value: requestId,
- type: "string",
- },
- },
- });
- }
-
- /**
- * Resets the entire action cache.
- */
- public async resetCache(): Promise {
- await super.resetCache();
- this.logger({
- category: "action_cache",
- message: "Action cache has been reset.",
- level: 1,
- });
- }
-}
diff --git a/lib/cache/BaseCache.ts b/lib/cache/BaseCache.ts
deleted file mode 100644
index 18ef61690..000000000
--- a/lib/cache/BaseCache.ts
+++ /dev/null
@@ -1,568 +0,0 @@
-import * as fs from "fs";
-import * as path from "path";
-import * as crypto from "crypto";
-import { LogLine } from "../../types/log";
-
-export interface CacheEntry {
- timestamp: number;
- data: unknown;
- requestId: string;
-}
-
-export interface CacheStore {
- [key: string]: CacheEntry;
-}
-
-export class BaseCache {
- private readonly CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 1 week in milliseconds
- private readonly CLEANUP_PROBABILITY = 0.01; // 1% chance
-
- protected cacheDir: string;
- protected cacheFile: string;
- protected lockFile: string;
- protected logger: (message: LogLine) => void;
-
- private readonly LOCK_TIMEOUT_MS = 1_000;
- protected lockAcquired = false;
- protected lockAcquireFailures = 0;
-
- // Added for request ID tracking
- protected requestIdToUsedHashes: { [key: string]: string[] } = {};
-
- constructor(
- logger: (message: LogLine) => void,
- cacheDir: string = path.join(process.cwd(), "tmp", ".cache"),
- cacheFile: string = "cache.json",
- ) {
- this.logger = logger;
- this.cacheDir = cacheDir;
- this.cacheFile = path.join(cacheDir, cacheFile);
- this.lockFile = path.join(cacheDir, "cache.lock");
- this.ensureCacheDirectory();
- this.setupProcessHandlers();
- }
-
- private setupProcessHandlers(): void {
- const releaseLockAndExit = () => {
- this.releaseLock();
- process.exit();
- };
-
- process.on("exit", releaseLockAndExit);
- process.on("SIGINT", releaseLockAndExit);
- process.on("SIGTERM", releaseLockAndExit);
- process.on("uncaughtException", (err) => {
- this.logger({
- category: "base_cache",
- message: "uncaught exception",
- level: 2,
- auxiliary: {
- error: {
- value: err.message,
- type: "string",
- },
- trace: {
- value: err.stack,
- type: "string",
- },
- },
- });
- if (this.lockAcquired) {
- releaseLockAndExit();
- }
- });
- }
-
- protected ensureCacheDirectory(): void {
- if (!fs.existsSync(this.cacheDir)) {
- fs.mkdirSync(this.cacheDir, { recursive: true });
- this.logger({
- category: "base_cache",
- message: "created cache directory",
- level: 1,
- auxiliary: {
- cacheDir: {
- value: this.cacheDir,
- type: "string",
- },
- },
- });
- }
- }
-
- protected createHash(data: unknown): string {
- const hash = crypto.createHash("sha256");
- return hash.update(JSON.stringify(data)).digest("hex");
- }
-
- protected sleep(ms: number): Promise {
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
-
- public async acquireLock(): Promise {
- const startTime = Date.now();
- while (Date.now() - startTime < this.LOCK_TIMEOUT_MS) {
- try {
- if (fs.existsSync(this.lockFile)) {
- const lockAge = Date.now() - fs.statSync(this.lockFile).mtimeMs;
- if (lockAge > this.LOCK_TIMEOUT_MS) {
- fs.unlinkSync(this.lockFile);
- this.logger({
- category: "base_cache",
- message: "Stale lock file removed",
- level: 1,
- });
- }
- }
-
- fs.writeFileSync(this.lockFile, process.pid.toString(), { flag: "wx" });
- this.lockAcquireFailures = 0;
- this.lockAcquired = true;
- this.logger({
- category: "base_cache",
- message: "Lock acquired",
- level: 1,
- });
- return true;
- } catch (e) {
- this.logger({
- category: "base_cache",
- message: "error acquiring lock",
- level: 2,
- auxiliary: {
- trace: {
- value: e.stack,
- type: "string",
- },
- message: {
- value: e.message,
- type: "string",
- },
- },
- });
- await this.sleep(5);
- }
- }
- this.logger({
- category: "base_cache",
- message: "Failed to acquire lock after timeout",
- level: 2,
- });
- this.lockAcquireFailures++;
- if (this.lockAcquireFailures >= 3) {
- this.logger({
- category: "base_cache",
- message:
- "Failed to acquire lock 3 times in a row. Releasing lock manually.",
- level: 1,
- });
- this.releaseLock();
- }
- return false;
- }
-
- public releaseLock(): void {
- try {
- if (fs.existsSync(this.lockFile)) {
- fs.unlinkSync(this.lockFile);
- this.logger({
- category: "base_cache",
- message: "Lock released",
- level: 1,
- });
- }
- this.lockAcquired = false;
- } catch (error) {
- this.logger({
- category: "base_cache",
- message: "error releasing lock",
- level: 2,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
- }
- }
-
- /**
- * Cleans up stale cache entries that exceed the maximum age.
- */
- public async cleanupStaleEntries(): Promise {
- if (!(await this.acquireLock())) {
- this.logger({
- category: "llm_cache",
- message: "failed to acquire lock for cleanup",
- level: 2,
- });
- return;
- }
-
- try {
- const cache = this.readCache();
- const now = Date.now();
- let entriesRemoved = 0;
-
- for (const [hash, entry] of Object.entries(cache)) {
- if (now - entry.timestamp > this.CACHE_MAX_AGE_MS) {
- delete cache[hash];
- entriesRemoved++;
- }
- }
-
- if (entriesRemoved > 0) {
- this.writeCache(cache);
- this.logger({
- category: "llm_cache",
- message: "cleaned up stale cache entries",
- level: 1,
- auxiliary: {
- entriesRemoved: {
- value: entriesRemoved.toString(),
- type: "integer",
- },
- },
- });
- }
- } catch (error) {
- this.logger({
- category: "llm_cache",
- message: "error during cache cleanup",
- level: 2,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
- } finally {
- this.releaseLock();
- }
- }
-
- protected readCache(): CacheStore {
- if (fs.existsSync(this.cacheFile)) {
- try {
- const data = fs.readFileSync(this.cacheFile, "utf-8");
- return JSON.parse(data) as CacheStore;
- } catch (error) {
- this.logger({
- category: "base_cache",
- message: "error reading cache file. resetting cache.",
- level: 1,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
- this.resetCache();
- return {};
- }
- }
- return {};
- }
-
- protected writeCache(cache: CacheStore): void {
- try {
- fs.writeFileSync(this.cacheFile, JSON.stringify(cache, null, 2));
- this.logger({
- category: "base_cache",
- message: "Cache written to file",
- level: 1,
- });
- } catch (error) {
- this.logger({
- category: "base_cache",
- message: "error writing cache file",
- level: 2,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
- } finally {
- this.releaseLock();
- }
- }
-
- /**
- * Retrieves data from the cache based on the provided options.
- * @param hashObj - The options used to generate the cache key.
- * @param requestId - The identifier for the current request.
- * @returns The cached data if available, otherwise null.
- */
- public async get(
- hashObj: Record | string,
- requestId: string,
- ): Promise {
- if (!(await this.acquireLock())) {
- this.logger({
- category: "base_cache",
- message: "Failed to acquire lock for getting cache",
- level: 2,
- });
- return null;
- }
-
- try {
- const hash = this.createHash(hashObj);
- const cache = this.readCache();
-
- if (cache[hash]) {
- this.trackRequestIdUsage(requestId, hash);
- return cache[hash].data;
- }
- return null;
- } catch (error) {
- this.logger({
- category: "base_cache",
- message: "error getting cache. resetting cache.",
- level: 1,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
-
- this.resetCache();
- return null;
- } finally {
- this.releaseLock();
- }
- }
-
- /**
- * Stores data in the cache based on the provided options and requestId.
- * @param hashObj - The options used to generate the cache key.
- * @param data - The data to be cached.
- * @param requestId - The identifier for the cache entry.
- */
- public async set(
- hashObj: Record,
- data: T["data"],
- requestId: string,
- ): Promise {
- if (!(await this.acquireLock())) {
- this.logger({
- category: "base_cache",
- message: "Failed to acquire lock for setting cache",
- level: 2,
- });
- return;
- }
-
- try {
- const hash = this.createHash(hashObj);
- const cache = this.readCache();
- cache[hash] = {
- data,
- timestamp: Date.now(),
- requestId,
- };
-
- this.writeCache(cache);
- this.trackRequestIdUsage(requestId, hash);
- } catch (error) {
- this.logger({
- category: "base_cache",
- message: "error setting cache. resetting cache.",
- level: 1,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
-
- this.resetCache();
- } finally {
- this.releaseLock();
-
- if (Math.random() < this.CLEANUP_PROBABILITY) {
- this.cleanupStaleEntries();
- }
- }
- }
-
- public async delete(hashObj: Record): Promise {
- if (!(await this.acquireLock())) {
- this.logger({
- category: "base_cache",
- message: "Failed to acquire lock for removing cache entry",
- level: 2,
- });
- return;
- }
-
- try {
- const hash = this.createHash(hashObj);
- const cache = this.readCache();
-
- if (cache[hash]) {
- delete cache[hash];
- this.writeCache(cache);
- } else {
- this.logger({
- category: "base_cache",
- message: "Cache entry not found to delete",
- level: 1,
- });
- }
- } catch (error) {
- this.logger({
- category: "base_cache",
- message: "error removing cache entry",
- level: 2,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
- } finally {
- this.releaseLock();
- }
- }
-
- /**
- * Tracks the usage of a hash with a specific requestId.
- * @param requestId - The identifier for the current request.
- * @param hash - The cache key hash.
- */
- protected trackRequestIdUsage(requestId: string, hash: string): void {
- this.requestIdToUsedHashes[requestId] ??= [];
- this.requestIdToUsedHashes[requestId].push(hash);
- }
-
- /**
- * Deletes all cache entries associated with a specific requestId.
- * @param requestId - The identifier for the request whose cache entries should be deleted.
- */
- public async deleteCacheForRequestId(requestId: string): Promise {
- if (!(await this.acquireLock())) {
- this.logger({
- category: "base_cache",
- message: "Failed to acquire lock for deleting cache",
- level: 2,
- });
- return;
- }
- try {
- const cache = this.readCache();
- const hashes = this.requestIdToUsedHashes[requestId] ?? [];
- let entriesRemoved = 0;
- for (const hash of hashes) {
- if (cache[hash]) {
- delete cache[hash];
- entriesRemoved++;
- }
- }
- if (entriesRemoved > 0) {
- this.writeCache(cache);
- } else {
- this.logger({
- category: "base_cache",
- message: "no cache entries found for requestId",
- level: 1,
- auxiliary: {
- requestId: {
- value: requestId,
- type: "string",
- },
- },
- });
- }
- // Remove the requestId from the mapping after deletion
- delete this.requestIdToUsedHashes[requestId];
- } catch (error) {
- this.logger({
- category: "base_cache",
- message: "error deleting cache for requestId",
- level: 2,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- requestId: {
- value: requestId,
- type: "string",
- },
- },
- });
- } finally {
- this.releaseLock();
- }
- }
-
- /**
- * Resets the entire cache by clearing the cache file.
- */
- public resetCache(): void {
- try {
- fs.writeFileSync(this.cacheFile, "{}");
- this.requestIdToUsedHashes = {}; // Reset requestId tracking
- } catch (error) {
- this.logger({
- category: "base_cache",
- message: "error resetting cache",
- level: 2,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
- } finally {
- this.releaseLock();
- }
- }
-}
diff --git a/lib/cache/LLMCache.ts b/lib/cache/LLMCache.ts
deleted file mode 100644
index 53bd78213..000000000
--- a/lib/cache/LLMCache.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { BaseCache, CacheEntry } from "./BaseCache";
-
-export class LLMCache extends BaseCache {
- constructor(
- logger: (message: {
- category?: string;
- message: string;
- level?: number;
- }) => void,
- cacheDir?: string,
- cacheFile?: string,
- ) {
- super(logger, cacheDir, cacheFile || "llm_calls.json");
- }
-
- /**
- * Overrides the get method to track used hashes by requestId.
- * @param options - The options used to generate the cache key.
- * @param requestId - The identifier for the current request.
- * @returns The cached data if available, otherwise null.
- */
- public async get(
- options: Record,
- requestId: string,
- ): Promise {
- const data = await super.get(options, requestId);
- return data as T | null; // TODO: remove this cast
- }
-
- /**
- * Overrides the set method to include cache cleanup logic.
- * @param options - The options used to generate the cache key.
- * @param data - The data to be cached.
- * @param requestId - The identifier for the current request.
- */
- public async set(
- options: Record,
- data: unknown,
- requestId: string,
- ): Promise {
- await super.set(options, data, requestId);
- this.logger({
- category: "llm_cache",
- message: "Cache miss - saved new response",
- level: 1,
- });
- }
-}
diff --git a/lib/dom/DomChunk.ts b/lib/dom/DomChunk.ts
deleted file mode 100644
index 0b2232efa..000000000
--- a/lib/dom/DomChunk.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export interface DomChunk {
- startOffset: number;
- endOffset: number;
- outputString: string;
- selectorMap: Record;
-}
diff --git a/lib/dom/ElementContainer.ts b/lib/dom/ElementContainer.ts
deleted file mode 100644
index c831e0489..000000000
--- a/lib/dom/ElementContainer.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { StagehandContainer } from "./StagehandContainer";
-
-/**
- * The ElementContainer class is a container implementation for a specific
- * HTML element.
- *
- * Unlike `GlobalPageContainer`, which manages the entire page,
- * this class focuses on one particular `HTMLElement`. Operations
- * such as `scrollTo` and `scrollIntoView` apply to that element
- * rather than `window`.
- */
-export class ElementContainer extends StagehandContainer {
- /**
- * Creates an instance of `ElementContainer` tied to a specific element.
- * @param el - The scrollable `HTMLElement` that this container controls.
- */
- constructor(private el: HTMLElement) {
- super();
- }
-
- public getRootElement(): HTMLElement {
- return this.el;
- }
-
- /**
- * Retrieves the height of the visible viewport within this element
- * (`el.clientHeight`).
- *
- * @returns The visible (client) height of the element, in pixels.
- */
- public getViewportHeight(): number {
- return this.el.clientHeight;
- }
-
- public getScrollHeight(): number {
- return this.el.scrollHeight;
- }
-
- /**
- * Returns the element's current vertical scroll offset.
- */
- public getScrollPosition(): number {
- return this.el.scrollTop;
- }
-
- /**
- * Smoothly scrolls this element to the specified vertical offset, and
- * waits for the scrolling to complete.
- *
- * @param offset - The scroll offset (in pixels) from the top of the element.
- * @returns A promise that resolves once scrolling is finished.
- */
- public async scrollTo(offset: number): Promise {
- await new Promise((resolve) => setTimeout(resolve, 1500));
- this.el.scrollTo({ top: offset, behavior: "smooth" });
- await this.waitForScrollEnd();
- }
-
- /**
- * Scrolls this element so that the given `element` is visible, or
- * scrolls to the top if none is provided. Smoothly animates the scroll
- * and waits until it finishes.
- *
- * @param element - The child element to bring into view. If omitted, scrolls to top.
- * @returns A promise that resolves once scrolling completes.
- */
- public async scrollIntoView(element?: HTMLElement): Promise {
- if (!element) {
- this.el.scrollTo({ top: 0, behavior: "smooth" });
- } else {
- element.scrollIntoView();
- }
- await this.waitForScrollEnd();
- }
-
- /**
- * Internal helper that waits until scrolling in this element has
- * fully stopped. It listens for scroll events on the element,
- * resetting a short timer every time a scroll occurs, and resolves
- * once there's no scroll for ~100ms.
- *
- * @returns A promise that resolves when scrolling has finished.
- */
- private async waitForScrollEnd(): Promise {
- return new Promise((resolve) => {
- let scrollEndTimer: number;
- const handleScroll = () => {
- clearTimeout(scrollEndTimer);
- scrollEndTimer = window.setTimeout(() => {
- this.el.removeEventListener("scroll", handleScroll);
- resolve();
- }, 100);
- };
- this.el.addEventListener("scroll", handleScroll, { passive: true });
- handleScroll();
- });
- }
-}
diff --git a/lib/dom/GlobalPageContainer.ts b/lib/dom/GlobalPageContainer.ts
deleted file mode 100644
index 46270fd1d..000000000
--- a/lib/dom/GlobalPageContainer.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import { StagehandContainer } from "./StagehandContainer";
-import { calculateViewportHeight } from "./utils";
-
-/**
- * The `GlobalPageContainer` class is a container implementation for the entire
- * webpage or global document.
- *
- * This container manages the global `window` scroll position and provides
- * measurements using `document.documentElement`. It extends `StagehandContainer`
- * to unify scrolling/height logic with other container types, such as element
- * based containers from the `ElementContainer` class.
- */
-export class GlobalPageContainer extends StagehandContainer {
- public getRootElement(): HTMLElement {
- return document.body;
- }
-
- /**
- * Calculates the viewport height for the entire page, using a helper.
- * The helper returns 75% of the window height, to ensure that we don't
- * miss any content that may be behind sticky elements like nav bars.
- *
- * @returns The current height of the global viewport, in pixels.
- */
- public getViewportHeight(): number {
- return calculateViewportHeight();
- }
-
- public getScrollHeight(): number {
- return document.documentElement.scrollHeight;
- }
-
- public getScrollPosition(): number {
- return window.scrollY;
- }
-
- /**
- * Smoothly scrolls the page to the specified vertical offset, and then
- * waits until scrolling has stopped. There is a delay built in to allow
- * for lazy loading and other asynchronous content to load.
- *
- * @param offset - The desired scroll offset from the top of the page.
- * @returns A promise that resolves once scrolling is complete.
- */
- public async scrollTo(offset: number): Promise {
- await new Promise((resolve) => setTimeout(resolve, 1500));
- window.scrollTo({ top: offset, behavior: "smooth" });
- await this.waitForScrollEnd();
- }
-
- /**
- * Scrolls the page so that a given element is visible, or scrolls to the top
- * if no element is specified. Uses smooth scrolling and waits for it to complete.
- *
- * @param element - The DOM element to bring into view. If omitted, scrolls to top.
- * @returns A promise that resolves once scrolling is complete.
- */
- public async scrollIntoView(element?: HTMLElement): Promise {
- if (!element) {
- window.scrollTo({ top: 0, behavior: "smooth" });
- } else {
- const rect = element.getBoundingClientRect();
- const currentY = window.scrollY || document.documentElement.scrollTop;
- const elementY = currentY + rect.top - window.innerHeight * 0.25;
- window.scrollTo({ top: elementY, behavior: "smooth" });
- }
- await this.waitForScrollEnd();
- }
-
- /**
- * Internal helper that waits until the global scroll activity has stopped.
- * It listens for scroll events, resetting a short timer every time a scroll
- * occurs, and resolves once there's no scroll for ~100ms.
- *
- * @returns A promise that resolves when scrolling has finished.
- */
- private async waitForScrollEnd(): Promise {
- return new Promise((resolve) => {
- let scrollEndTimer: number;
- const handleScroll = () => {
- clearTimeout(scrollEndTimer);
- scrollEndTimer = window.setTimeout(() => {
- window.removeEventListener("scroll", handleScroll);
- resolve();
- }, 100);
- };
- window.addEventListener("scroll", handleScroll, { passive: true });
- handleScroll();
- });
- }
-}
diff --git a/lib/dom/StagehandContainer.ts b/lib/dom/StagehandContainer.ts
deleted file mode 100644
index 94e107e93..000000000
--- a/lib/dom/StagehandContainer.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import { DomChunk } from "@/lib/dom/DomChunk";
-import { collectCandidateElements } from "@/lib/dom/candidateCollector";
-
-/**
- * The `StagehandContainer` class defines an abstract interface for
- * scrolling and DOM inspection across various container types e.g.,
- * the entire page (GlobalPageContainer), a specific sub element of the DOM,
- * (ElementContainer).
- */
-export abstract class StagehandContainer {
- public abstract getViewportHeight(): number;
- public abstract getScrollHeight(): number;
- public abstract scrollTo(offset: number): Promise;
- public abstract getRootElement(): HTMLElement | Document;
- public abstract scrollIntoView(element?: HTMLElement): Promise;
- public abstract getScrollPosition(): number;
-
- /**
- * Collects multiple "DOM chunks" by scrolling through the container
- * in increments from `startOffset` to `endOffset`. At each scroll
- * position, the function extracts a snapshot of "candidate elements"
- * using `collectCandidateElements`.
- *
- * Each chunk represents a subset of the DOM at a particular
- * vertical scroll offset, including:
- *
- * - `startOffset` & `endOffset`: The vertical scroll bounds for this chunk.
- * - `outputString`: A serialized representation of extracted DOM text.
- * - `selectorMap`: A mapping of temporary indices to the actual element(s)
- * that were collected in this chunk, useful for further processing.
- *
- * @param startOffset - The initial scroll offset from which to begin collecting.
- * @param endOffset - The maximum scroll offset to collect up to.
- * @param chunkSize - The vertical increment to move between each chunk.
- * @param scrollTo - Whether we should scroll to the chunk
- * @param scrollBackToTop - Whether to scroll the container back to the top once finished.
- * @param candidateContainer - Optionally, a specific container element within
- * the root for which to collect data. If omitted, uses `this.getRootElement()`.
- *
- * @returns A promise that resolves with an array of `DomChunk` objects.
- *
- * ### How It Works
- *
- * 1. **Scroll Range Calculation**:
- * - Computes `maxOffset` as the maximum offset that can be scrolled
- * (`scrollHeight - viewportHeight`).
- * - Restricts `endOffset` to not exceed `maxOffset`.
- *
- * 2. **Chunk Iteration**:
- * - Loops from `startOffset` to `endOffset` in steps of `chunkSize`.
- * - For each offset `current`, we call `this.scrollTo(current)`
- * to position the container.
- *
- * 3. **Element Collection**:
- * - Invokes `collectCandidateElements` on either `candidateContainer`
- * (if provided) or the result of `this.getRootElement()`.
- * - This returns both an `outputString` (serialized text)
- * and a `selectorMap` of found elements for that section of the DOM.
- *
- * 4. **Chunk Assembly**:
- * - Creates a `DomChunk` object for the current offset range,
- * storing `outputString`, `selectorMap`, and scroll offsets.
- * - Pushes it onto the `chunks` array.
- *
- * 5. **Scroll Reset**:
- * - Once iteration completes, if `scrollBackToTop` is `true`,
- * we scroll back to offset `0`.
- */
- public async collectDomChunks(
- startOffset: number,
- endOffset: number,
- chunkSize: number,
- scrollTo: boolean = true,
- scrollBackToTop: boolean = true,
- candidateContainer?: HTMLElement,
- ): Promise {
- const chunks: DomChunk[] = [];
- let maxOffset = this.getScrollHeight();
- let current = startOffset;
- let finalEnd = endOffset;
-
- let index = 0;
-
- while (current <= finalEnd) {
- // Move the container's scroll position
- if (scrollTo) {
- await this.scrollTo(current);
- }
-
- // Collect the candidate elements at this offset
- const rootCandidate =
- candidateContainer || (this.getRootElement() as HTMLElement);
- const { outputString, selectorMap } = await collectCandidateElements(
- rootCandidate,
- index,
- );
-
- chunks.push({
- startOffset: current,
- endOffset: current + chunkSize,
- outputString,
- selectorMap,
- });
-
- index += Object.keys(selectorMap).length;
- current += chunkSize;
-
- // Only extend finalEnd if there is no candidateContainer
- // (meaning we're looking at the entire scrollable area)
- if (!candidateContainer && current > endOffset) {
- // Check if new content extended the scroll height
- const newScrollHeight = this.getScrollHeight();
- if (newScrollHeight > maxOffset) {
- maxOffset = newScrollHeight;
- }
- if (newScrollHeight > finalEnd) {
- finalEnd = newScrollHeight;
- }
- }
- }
-
- if (scrollBackToTop) {
- await this.scrollTo(0);
- }
-
- return chunks;
- }
-}
diff --git a/lib/dom/candidateCollector.ts b/lib/dom/candidateCollector.ts
deleted file mode 100644
index e577653f1..000000000
--- a/lib/dom/candidateCollector.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import {
- isElementNode,
- isTextNode,
- isInteractiveElement,
- isLeafElement,
- isVisible,
- isTextVisible,
- isActive,
-} from "./elementCheckUtils";
-import { generateXPathsForElement as generateXPaths } from "./xpathUtils";
-
-const xpathCache: Map = new Map();
-
-/**
- * `collectCandidateElements` performs a depth-first traversal (despite the BFS naming) of the given `candidateContainerRoot`
- * to find “candidate elements” or text nodes that meet certain criteria (e.g., visible, active,
- * interactive, or leaf). This function does not scroll the page; it only collects nodes from
- * the in-memory DOM structure starting at `candidateContainerRoot`.
- *
- * @param candidateContainerRoot - The HTMLElement to search within
- * @param indexOffset - A numeric offset used to label/number your candidate elements
- * @returns { outputString, selectorMap }
- */
-export async function collectCandidateElements(
- candidateContainerRoot: HTMLElement,
- indexOffset: number = 0,
-): Promise<{
- outputString: string;
- selectorMap: Record;
-}> {
- const DOMQueue: ChildNode[] = [...candidateContainerRoot.childNodes];
- const candidateElements: ChildNode[] = [];
-
- while (DOMQueue.length > 0) {
- const node = DOMQueue.pop();
- let shouldAdd = false;
-
- if (node && isElementNode(node)) {
- for (let i = node.childNodes.length - 1; i >= 0; i--) {
- DOMQueue.push(node.childNodes[i]);
- }
-
- if (isInteractiveElement(node)) {
- if (isActive(node) && isVisible(node)) {
- shouldAdd = true;
- }
- }
- if (isLeafElement(node)) {
- if (isActive(node) && isVisible(node)) {
- shouldAdd = true;
- }
- }
- }
-
- if (node && isTextNode(node) && isTextVisible(node)) {
- shouldAdd = true;
- }
-
- if (shouldAdd) {
- candidateElements.push(node);
- }
- }
-
- const selectorMap: Record = {};
- let outputString = "";
-
- const xpathLists = await Promise.all(
- candidateElements.map((elem) => {
- if (xpathCache.has(elem)) {
- return Promise.resolve(xpathCache.get(elem)!);
- }
- return generateXPaths(elem).then((xpaths: string[]) => {
- xpathCache.set(elem, xpaths);
- return xpaths;
- });
- }),
- );
-
- candidateElements.forEach((elem, idx) => {
- const xpaths = xpathLists[idx];
- let elemOutput = "";
-
- if (isTextNode(elem)) {
- const textContent = elem.textContent?.trim();
- if (textContent) {
- elemOutput += `${idx + indexOffset}:${textContent}\n`;
- }
- } else if (isElementNode(elem)) {
- const tagName = elem.tagName.toLowerCase();
- const attributes = collectEssentialAttributes(elem);
- const opening = `<${tagName}${attributes ? " " + attributes : ""}>`;
- const closing = `${tagName}>`;
- const textContent = elem.textContent?.trim() || "";
- elemOutput += `${idx + indexOffset}:${opening}${textContent}${closing}\n`;
- }
-
- outputString += elemOutput;
- selectorMap[idx + indexOffset] = xpaths;
- });
-
- return { outputString, selectorMap };
-}
-
-/**
- * Collects essential attributes from an element.
- * @param element The DOM element.
- * @returns A string of formatted attributes.
- */
-function collectEssentialAttributes(element: Element): string {
- const essentialAttributes = [
- "id",
- "class",
- "href",
- "src",
- "aria-label",
- "aria-name",
- "aria-role",
- "aria-description",
- "aria-expanded",
- "aria-haspopup",
- "type",
- "value",
- ];
-
- const attrs: string[] = essentialAttributes
- .map((attr) => {
- const value = element.getAttribute(attr);
- return value ? `${attr}="${value}"` : "";
- })
- .filter((attr) => attr !== "");
-
- Array.from(element.attributes).forEach((attr) => {
- if (attr.name.startsWith("data-")) {
- attrs.push(`${attr.name}="${attr.value}"`);
- }
- });
-
- return attrs.join(" ");
-}
diff --git a/lib/dom/containerFactory.ts b/lib/dom/containerFactory.ts
deleted file mode 100644
index 2375a5737..000000000
--- a/lib/dom/containerFactory.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { StagehandContainer } from "./StagehandContainer";
-import { GlobalPageContainer } from "./GlobalPageContainer";
-import { ElementContainer } from "./ElementContainer";
-
-/**
- * Decide which container to create.
- */
-export function createStagehandContainer(
- obj: Window | HTMLElement,
-): StagehandContainer {
- if (obj instanceof Window) {
- return new GlobalPageContainer();
- } else {
- return new ElementContainer(obj);
- }
-}
diff --git a/lib/dom/elementCheckUtils.ts b/lib/dom/elementCheckUtils.ts
deleted file mode 100644
index b62637730..000000000
--- a/lib/dom/elementCheckUtils.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-export function isElementNode(node: Node): node is Element {
- return node.nodeType === Node.ELEMENT_NODE;
-}
-
-export function isTextNode(node: Node): node is Text {
- return node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim());
-}
-
-const leafElementDenyList = ["SVG", "IFRAME", "SCRIPT", "STYLE", "LINK"];
-
-const interactiveElementTypes = [
- "A",
- "BUTTON",
- "DETAILS",
- "EMBED",
- "INPUT",
- "LABEL",
- "MENU",
- "MENUITEM",
- "OBJECT",
- "SELECT",
- "TEXTAREA",
- "SUMMARY",
-];
-
-const interactiveRoles = [
- "button",
- "menu",
- "menuitem",
- "link",
- "checkbox",
- "radio",
- "slider",
- "tab",
- "tabpanel",
- "textbox",
- "combobox",
- "grid",
- "listbox",
- "option",
- "progressbar",
- "scrollbar",
- "searchbox",
- "switch",
- "tree",
- "treeitem",
- "spinbutton",
- "tooltip",
-];
-const interactiveAriaRoles = ["menu", "menuitem", "button"];
-
-/*
- * Checks if an element is visible and therefore relevant for LLMs to consider. We check:
- * - Size
- * - Display properties
- * - Opacity
- * If the element is a child of a previously hidden element, it should not be included, so we don't consider downstream effects of a parent element here
- */
-export const isVisible = (element: Element) => {
- const rect = element.getBoundingClientRect();
- // Ensure the element is within the viewport
- if (
- rect.width === 0 ||
- rect.height === 0 ||
- rect.top < 0 ||
- rect.top > window.innerHeight
- ) {
- return false;
- }
- if (!isTopElement(element, rect)) {
- return false;
- }
-
- const visible = element.checkVisibility({
- checkOpacity: true,
- checkVisibilityCSS: true,
- });
-
- return visible;
-};
-
-export const isTextVisible = (element: ChildNode) => {
- const range = document.createRange();
- range.selectNodeContents(element);
- const rect = range.getBoundingClientRect();
-
- if (
- rect.width === 0 ||
- rect.height === 0 ||
- rect.top < 0 ||
- rect.top > window.innerHeight
- ) {
- return false;
- }
- const parent = element.parentElement;
- if (!parent) {
- return false;
- }
-
- const visible = parent.checkVisibility({
- checkOpacity: true,
- checkVisibilityCSS: true,
- });
-
- return visible;
-};
-
-export function isTopElement(elem: ChildNode, rect: DOMRect) {
- const points = [
- { x: rect.left + rect.width * 0.25, y: rect.top + rect.height * 0.25 },
- { x: rect.left + rect.width * 0.75, y: rect.top + rect.height * 0.25 },
- { x: rect.left + rect.width * 0.25, y: rect.top + rect.height * 0.75 },
- { x: rect.left + rect.width * 0.75, y: rect.top + rect.height * 0.75 },
- { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 },
- ];
-
- return points.some((point) => {
- const topEl = document.elementFromPoint(point.x, point.y);
- let current = topEl;
- while (current && current !== document.body) {
- if (current.isSameNode(elem)) {
- return true;
- }
- current = current.parentElement;
- }
- return false;
- });
-}
-
-export const isActive = (element: Element) => {
- if (
- element.hasAttribute("disabled") ||
- element.hasAttribute("hidden") ||
- element.getAttribute("aria-disabled") === "true"
- ) {
- return false;
- }
-
- return true;
-};
-export const isInteractiveElement = (element: Element) => {
- const elementType = element.tagName;
- const elementRole = element.getAttribute("role");
- const elementAriaRole = element.getAttribute("aria-role");
-
- return (
- (elementType && interactiveElementTypes.includes(elementType)) ||
- (elementRole && interactiveRoles.includes(elementRole)) ||
- (elementAriaRole && interactiveAriaRoles.includes(elementAriaRole))
- );
-};
-
-export const isLeafElement = (element: Element) => {
- if (element.textContent === "") {
- return false;
- }
-
- if (element.childNodes.length === 0) {
- return !leafElementDenyList.includes(element.tagName);
- }
-
- // This case ensures that extra context will be included for simple element nodes that contain only text
- if (element.childNodes.length === 1 && isTextNode(element.childNodes[0])) {
- return true;
- }
-
- return false;
-};
diff --git a/lib/dom/genDomScripts.ts b/lib/dom/genDomScripts.ts
deleted file mode 100644
index d3c3edc1c..000000000
--- a/lib/dom/genDomScripts.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * We have a collection of typescript functions that we need to run in the browser.
- * First, we build them into a single js file
- * Second, due to framework differences we need to get our script content as a string to avoid pathing issues due to file routing in frameworks like Next.js
- * Playwright allows us to pass in script content directly as a string instead of reading a file from a path
- * https://github.com/browserbase/stagehand/issues/180
- *
- * We can't rely on the normal build process for stagehand, because we need our script content as a string so that the import *just works*
- */
-import fs from "fs";
-import path from "path";
-import esbuild from "esbuild";
-
-fs.mkdirSync(path.join(__dirname, "./build"), { recursive: true });
-
-esbuild.buildSync({
- entryPoints: [path.join(__dirname, "index.ts")],
- bundle: true,
- outdir: path.join(__dirname, "build"),
-});
-
-const scriptContent = fs.readFileSync(
- path.join(__dirname, "./build/index.js"),
- "utf8",
-);
-
-const output = `export const scriptContent = ${JSON.stringify(scriptContent)};`;
-
-fs.writeFileSync(path.join(__dirname, "./build/scriptContent.ts"), output);
diff --git a/lib/dom/global.d.ts b/lib/dom/global.d.ts
deleted file mode 100644
index 742c15433..000000000
--- a/lib/dom/global.d.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { StagehandContainer } from "./StagehandContainer";
-
-export {};
-declare global {
- interface Window {
- chunkNumber: number;
- showChunks?: boolean;
- processDom: (chunksSeen: Array) => Promise<{
- outputString: string;
- selectorMap: Record;
- chunk: number;
- chunks: number[];
- }>;
- processAllOfDom: (xpath?: string) => Promise<{
- outputString: string;
- selectorMap: Record;
- }>;
- createStagehandContainer: (obj: Window | HTMLElement) => StagehandContainer;
- waitForDomSettle: () => Promise;
- __playwright?: unknown;
- __pw_manual?: unknown;
- __PW_inspect?: unknown;
- storeDOM: (xpath?: string) => string;
- restoreDOM: (storedDOM: string, xpath?: string) => void;
- createTextBoundingBoxes: (xpath?: string) => void;
- getElementBoundingBoxes: (xpath: string) => Array<{
- text: string;
- top: number;
- left: number;
- width: number;
- height: number;
- }>;
- getScrollableElementXpaths: (topN?: number) => Promise;
- getNodeFromXpath: (xpath: string) => Node | null;
- waitForElementScrollEnd: (element: HTMLElement) => Promise;
- }
-}
diff --git a/lib/dom/index.ts b/lib/dom/index.ts
deleted file mode 100644
index 96b53216a..000000000
--- a/lib/dom/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from "./process";
-export * from "./utils";
diff --git a/lib/dom/process.ts b/lib/dom/process.ts
deleted file mode 100644
index c9aa28408..000000000
--- a/lib/dom/process.ts
+++ /dev/null
@@ -1,567 +0,0 @@
-import { generateXPathsForElement as generateXPaths } from "./xpathUtils";
-import {
- calculateViewportHeight,
- canElementScroll,
- getNodeFromXpath,
- waitForDomSettle,
- waitForElementScrollEnd,
-} from "./utils";
-import { createStagehandContainer } from "./containerFactory";
-import { StagehandContainer } from "./StagehandContainer";
-import { GlobalPageContainer } from "@/lib/dom/GlobalPageContainer";
-import { ElementContainer } from "@/lib/dom/ElementContainer";
-import { DomChunk } from "@/lib/dom/DomChunk";
-import { StagehandDomProcessError } from "@/types/stagehandErrors";
-
-/**
- * Finds and returns a list of scrollable elements on the page,
- * ordered from the element with the largest scrollHeight to the smallest.
- *
- * @param topN Optional maximum number of scrollable elements to return.
- * If not provided, all found scrollable elements are returned.
- * @returns An array of HTMLElements sorted by descending scrollHeight.
- */
-export function getScrollableElements(topN?: number): HTMLElement[] {
- // Get the root element
- const docEl = document.documentElement;
-
- // 1) Initialize an array to hold all scrollable elements.
- // Always include the root element as a fallback.
- const scrollableElements: HTMLElement[] = [docEl];
-
- // 2) Scan all elements to find potential scrollable containers.
- // A candidate must have a scrollable overflow style and extra scrollable content.
- const allElements = document.querySelectorAll("*");
- for (const elem of allElements) {
- const style = window.getComputedStyle(elem);
- const overflowY = style.overflowY;
-
- const isPotentiallyScrollable =
- overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";
-
- if (isPotentiallyScrollable) {
- const candidateScrollDiff = elem.scrollHeight - elem.clientHeight;
- // Only consider this element if it actually has extra scrollable content
- // and it can truly scroll.
- if (candidateScrollDiff > 0 && canElementScroll(elem)) {
- scrollableElements.push(elem);
- }
- }
- }
-
- // 3) Sort the scrollable elements from largest scrollHeight to smallest.
- scrollableElements.sort((a, b) => b.scrollHeight - a.scrollHeight);
-
- // 4) If a topN limit is specified, return only the first topN elements.
- if (topN !== undefined) {
- return scrollableElements.slice(0, topN);
- }
-
- // Return all found scrollable elements if no limit is provided.
- return scrollableElements;
-}
-
-/**
- * Calls getScrollableElements, then for each element calls generateXPaths,
- * and returns the first XPath for each.
- *
- * @param topN (optional) integer limit on how many scrollable elements to process
- * @returns string[] list of XPaths (1 for each scrollable element)
- */
-export async function getScrollableElementXpaths(
- topN?: number,
-): Promise {
- const scrollableElems = getScrollableElements(topN);
- const xpaths = [];
- for (const elem of scrollableElems) {
- const allXPaths = await generateXPaths(elem);
- const firstXPath = allXPaths?.[0] || "";
- xpaths.push(firstXPath);
- }
- return xpaths;
-}
-
-export function getNearestScrollableParent(el: HTMLElement): HTMLElement {
- // 1) Get *all* scrollable elements on the page
- // (You could pass a large topN or omit it for “all”)
- const allScrollables = getScrollableElements();
-
- // 2) Climb up the DOM tree
- let current: HTMLElement | null = el;
- while (current) {
- // If `current` is in the scrollable list, we have our nearest scrollable parent
- if (allScrollables.includes(current)) {
- return current;
- }
- current = current.parentElement;
- }
-
- // 3) If we exhaust the ancestors, default to root
- return document.documentElement;
-}
-
-/**
- * processDom
- * ----------
- * This function now just picks a single chunk index,
- * creates a container, and calls collectDomChunk to get a single DomChunk.
- *
- * @param chunksSeen - The chunks we've seen so far
- */
-export async function processDom(chunksSeen: number[]) {
- // 1) choose a chunk index from chunksSeen
- const { chunk, chunksArray } = await pickChunk(chunksSeen);
-
- // 2) create a container for the entire page
- const container = new GlobalPageContainer();
-
- // 3) pick a chunk size, e.g. container.getViewportHeight()
- const chunkSize = container.getViewportHeight();
-
- // 4) compute startOffset from `chunkIndex * chunkSize`
- const startOffset = chunk * chunkSize;
- // if we only want a single chunk, pass the same value for `endOffset`
- const endOffset = startOffset;
-
- // 5) collectAllDomChunks with identical start & end => exactly 1 iteration
- const domChunks = await container.collectDomChunks(
- startOffset,
- endOffset,
- chunkSize,
- true,
- false, // scrollBackToTop
- container.getRootElement(), // BFS entire doc
- );
-
- // We expect exactly 1 chunk
- const [domChunk] = domChunks;
- if (!domChunk) {
- // fallback, no chunk
- return {
- outputString: "",
- selectorMap: {},
- chunk,
- chunks: chunksArray,
- };
- }
-
- console.log("Extracted DOM chunk:\n", domChunk.outputString);
-
- return {
- outputString: domChunk.outputString,
- selectorMap: domChunk.selectorMap,
- chunk,
- chunks: chunksArray,
- };
-}
-
-/**
- * processAllOfDom
- * ---------------
- * If an xpath is provided, we find that element and build an appropriate container
- * (global or element) based on the nearest scrollable parent. Then we chunk from
- * startOffset..endOffset. We BFS within the element's subtree (candidateContainer).
- */
-export async function processAllOfDom(xpath?: string) {
- let candidateElementContainer: HTMLElement | null = null;
- let scrollTarget: StagehandContainer;
-
- if (xpath) {
- // 1) Find the element
- const node = getNodeFromXpath(xpath) as HTMLElement | null;
-
- if (node) {
- candidateElementContainer = node;
- console.log(`Found element via XPath: ${xpath}`);
-
- // 2) Always find the nearest scrollable parent
- const scrollableElem = getNearestScrollableParent(
- candidateElementContainer,
- );
- if (scrollableElem === document.documentElement) {
- scrollTarget = new GlobalPageContainer();
- } else {
- scrollTarget = new ElementContainer(scrollableElem);
- }
-
- // 3) Scroll the candidateElementContainer into view
- // (use the container's scroll logic)
- await scrollTarget.scrollIntoView(candidateElementContainer);
-
- // 4) Measure the container’s new scroll offset AFTER we've scrolled
- const startOffset = scrollTarget.getScrollPosition();
-
- // Now check if the element “fits” in the container’s viewport
- const scrollTargetHeight = scrollTarget.getViewportHeight();
- const candidateElementContainerHeight =
- candidateElementContainer.scrollHeight;
-
- if (candidateElementContainerHeight <= scrollTargetHeight) {
- // Single-chunk approach
- console.log(
- "Element is smaller/equal to container’s viewport. Doing single chunk.",
- );
-
- // We'll define a "single-chunk" scenario by telling
- // the container to gather from startOffset..startOffset
- // so it only processes exactly one chunk iteration
- const domChunks = await scrollTarget.collectDomChunks(
- startOffset, // startOffset
- startOffset, // endOffset => same as start => 1 chunk
- 1, // chunkSize=1 => doesn't matter, because start==end means exactly 1 iteration
- true,
- true,
- candidateElementContainer,
- );
-
- const singleChunkOutput = combineChunks(domChunks);
- console.log(
- "Final output (single-chunk):",
- singleChunkOutput.outputString,
- );
- return singleChunkOutput;
- }
-
- console.log("Element is bigger. Doing multi-chunk approach.");
- } else {
- console.warn(`XPath not found: ${xpath}. Using entire doc.`);
- }
- } else {
- const scrollableElems = getScrollableElements(1);
- const mainScrollable = scrollableElems[0];
-
- scrollTarget =
- mainScrollable === document.documentElement
- ? createStagehandContainer(window)
- : createStagehandContainer(mainScrollable);
- }
-
- const startOffset = scrollTarget.getScrollPosition();
- const viewportHeight = scrollTarget.getViewportHeight();
- const maxScroll = candidateElementContainer
- ? startOffset + candidateElementContainer.scrollHeight
- : scrollTarget.getScrollHeight();
- const chunkSize = viewportHeight;
-
- console.log("processAllOfDom chunk-based from", startOffset, "to", maxScroll);
-
- const domChunks = await scrollTarget.collectDomChunks(
- startOffset,
- maxScroll,
- chunkSize,
- true,
- true,
- candidateElementContainer ?? undefined,
- );
-
- const finalOutput = combineChunks(domChunks);
- console.log(
- "All DOM elements combined (chunk-based):",
- finalOutput.outputString,
- );
- return finalOutput;
-}
-
-function combineChunks(domChunks: DomChunk[]): {
- outputString: string;
- selectorMap: Record;
-} {
- const outputString = domChunks.map((c: DomChunk) => c.outputString).join("");
- let finalSelectorMap: Record = {};
- domChunks.forEach((c: DomChunk) => {
- finalSelectorMap = { ...finalSelectorMap, ...c.selectorMap };
- });
- return { outputString, selectorMap: finalSelectorMap };
-}
-
-/**
- * Stores either the entire DOM (if no `xpath` is given),
- * or the specific element that `xpath` points to.
- * Returns the outer HTML of what is stored.
- */
-export function storeDOM(xpath?: string): string {
- if (!xpath) {
- const originalDOM = document.body.cloneNode(true) as HTMLElement;
- console.log("DOM state stored (root).");
- return originalDOM.outerHTML;
- } else {
- const node = getNodeFromXpath(xpath) as HTMLElement | null;
-
- if (!node) {
- console.error(
- `storeDOM: No element found for xpath: ${xpath}. Returning empty string.`,
- );
- return "";
- }
-
- console.log(`DOM state stored (element at xpath: ${xpath}).`);
- return node.outerHTML;
- }
-}
-
-/**
- * Restores either the entire DOM (if no `xpath` is given),
- * or the specific element that `xpath` points to, based on `storedDOM`.
- */
-export function restoreDOM(storedDOM: string, xpath?: string): void {
- console.log("Restoring DOM...");
-
- if (!storedDOM) {
- console.error("No DOM state was provided.");
- return;
- }
-
- if (!xpath) {
- document.body.innerHTML = storedDOM;
- console.log("DOM restored (root).");
- } else {
- const node = getNodeFromXpath(xpath) as HTMLElement | null;
-
- if (!node) {
- console.error(
- `restoreDOM: No element found for xpath: ${xpath}. Cannot restore.`,
- );
- return;
- }
-
- node.outerHTML = storedDOM;
- console.log(`DOM restored (element at xpath: ${xpath}).`);
- }
-}
-
-export function createTextBoundingBoxes(xpath?: string): void {
- const style = document.createElement("style");
- document.head.appendChild(style);
- if (style.sheet) {
- style.sheet.insertRule(
- `
- .stagehand-highlighted-word, .stagehand-space {
- border: 0px solid orange;
- display: inline-block !important;
- visibility: visible;
- }
- `,
- 0,
- );
-
- style.sheet.insertRule(
- `
- code .stagehand-highlighted-word, code .stagehand-space,
- pre .stagehand-highlighted-word, pre .stagehand-space {
- white-space: pre-wrap;
- display: inline !important;
- }
- `,
- 1,
- );
- }
-
- /**
- * Applies highlighting to every text node under `root`.
- * If `root` is the entire `document`, we query "body *".
- * If `root` is a specific `HTMLElement`, we query "*".
- */
- function applyHighlighting(root: Document | HTMLElement): void {
- // If root is a Document, highlight everything under . Otherwise, highlight all children of that HTMLElement.
- const containerSelector = root instanceof Document ? "body *" : "*";
-
- root.querySelectorAll(containerSelector).forEach((element) => {
- if (
- element.closest &&
- element.closest(".stagehand-nav, .stagehand-marker")
- ) {
- return;
- }
- if (["SCRIPT", "STYLE", "IFRAME", "INPUT"].includes(element.tagName)) {
- return;
- }
-
- const childNodes = Array.from(element.childNodes);
- childNodes.forEach((node) => {
- if (node.nodeType === 3 && node.textContent?.trim().length > 0) {
- const textContent = node.textContent.replace(/\u00A0/g, " ");
- const tokens = textContent.split(/(\s+)/g); // Split text by spaces
- const fragment = document.createDocumentFragment();
- const parentIsCode = element.tagName === "CODE";
-
- tokens.forEach((token) => {
- const span = document.createElement("span");
- span.textContent = token;
- if (parentIsCode) {
- // Special handling for tags
- span.style.whiteSpace = "pre-wrap";
- span.style.display = "inline";
- }
- span.className =
- token.trim().length === 0
- ? "stagehand-space"
- : "stagehand-highlighted-word";
- fragment.appendChild(span);
- });
-
- if (fragment.childNodes.length > 0 && node.parentNode) {
- element.insertBefore(fragment, node);
- node.remove();
- }
- }
- });
- });
- }
-
- if (!xpath) {
- applyHighlighting(document);
-
- document.querySelectorAll("iframe").forEach((iframe) => {
- try {
- iframe.contentWindow?.postMessage({ action: "highlight" }, "*");
- } catch (error) {
- console.error("Error accessing iframe content: ", error);
- }
- });
- } else {
- const node = getNodeFromXpath(xpath) as HTMLElement | null;
-
- if (!node) {
- console.warn(
- `createTextBoundingBoxes: No element found for xpath "${xpath}".`,
- );
- return;
- }
-
- applyHighlighting(node);
- }
-}
-
-export function getElementBoundingBoxes(xpath: string): Array<{
- text: string;
- top: number;
- left: number;
- width: number;
- height: number;
-}> {
- const element = getNodeFromXpath(xpath) as HTMLElement;
-
- if (!element) return [];
-
- const isValidText = (text: string) => text && text.trim().length > 0;
- let dropDownElem = element.querySelector("option[selected]");
-
- if (!dropDownElem) {
- dropDownElem = element.querySelector("option");
- }
-
- if (dropDownElem) {
- const elemText = dropDownElem.textContent || "";
- if (isValidText(elemText)) {
- const parentRect = element.getBoundingClientRect();
- return [
- {
- text: elemText.trim(),
- top: parentRect.top + window.scrollY,
- left: parentRect.left + window.scrollX,
- width: parentRect.width,
- height: parentRect.height,
- },
- ];
- } else {
- return [];
- }
- }
-
- let placeholderText = "";
- if (
- (element.tagName.toLowerCase() === "input" ||
- element.tagName.toLowerCase() === "textarea") &&
- (element as HTMLInputElement).placeholder
- ) {
- placeholderText = (element as HTMLInputElement).placeholder;
- } else if (element.tagName.toLowerCase() === "a") {
- placeholderText = "";
- } else if (element.tagName.toLowerCase() === "img") {
- placeholderText = (element as HTMLImageElement).alt || "";
- }
-
- const words = element.querySelectorAll(
- ".stagehand-highlighted-word",
- ) as NodeListOf;
-
- const boundingBoxes = Array.from(words)
- .map((word) => {
- const rect = word.getBoundingClientRect();
- return {
- text: word.innerText || "",
- top: rect.top + window.scrollY,
- left: rect.left + window.scrollX,
- width: rect.width,
- height: rect.height * 0.75,
- };
- })
- .filter(
- (box) =>
- box.width > 0 &&
- box.height > 0 &&
- box.top >= 0 &&
- box.left >= 0 &&
- isValidText(box.text),
- );
-
- if (boundingBoxes.length === 0) {
- const elementRect = element.getBoundingClientRect();
- return [
- {
- text: placeholderText,
- top: elementRect.top + window.scrollY,
- left: elementRect.left + window.scrollX,
- width: elementRect.width,
- height: elementRect.height * 0.75,
- },
- ];
- }
-
- return boundingBoxes;
-}
-
-window.waitForDomSettle = waitForDomSettle;
-window.processDom = processDom;
-window.processAllOfDom = processAllOfDom;
-window.storeDOM = storeDOM;
-window.restoreDOM = restoreDOM;
-window.createTextBoundingBoxes = createTextBoundingBoxes;
-window.getElementBoundingBoxes = getElementBoundingBoxes;
-window.createStagehandContainer = createStagehandContainer;
-window.getScrollableElementXpaths = getScrollableElementXpaths;
-window.getNodeFromXpath = getNodeFromXpath;
-window.waitForElementScrollEnd = waitForElementScrollEnd;
-
-async function pickChunk(chunksSeen: Array) {
- const viewportHeight = calculateViewportHeight();
- const documentHeight = document.documentElement.scrollHeight;
-
- const chunks = Math.ceil(documentHeight / viewportHeight);
-
- const chunksArray = Array.from({ length: chunks }, (_, i) => i);
- const chunksRemaining = chunksArray.filter((chunk) => {
- return !chunksSeen.includes(chunk);
- });
-
- const currentScrollPosition = window.scrollY;
- const closestChunk = chunksRemaining.reduce((closest, current) => {
- const currentChunkTop = viewportHeight * current;
- const closestChunkTop = viewportHeight * closest;
- return Math.abs(currentScrollPosition - currentChunkTop) <
- Math.abs(currentScrollPosition - closestChunkTop)
- ? current
- : closest;
- }, chunksRemaining[0]);
- const chunk = closestChunk;
-
- if (chunk === undefined) {
- throw new StagehandDomProcessError(
- `No chunks remaining to check: ${chunksRemaining}`,
- );
- }
- return {
- chunk,
- chunksArray,
- };
-}
diff --git a/lib/dom/utils.ts b/lib/dom/utils.ts
deleted file mode 100644
index 65255578c..000000000
--- a/lib/dom/utils.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import { StagehandDomProcessError } from "@/types/stagehandErrors";
-
-export async function waitForDomSettle() {
- return new Promise((resolve) => {
- const createTimeout = () => {
- return setTimeout(() => {
- resolve();
- }, 2000);
- };
- let timeout = createTimeout();
- const observer = new MutationObserver(() => {
- clearTimeout(timeout);
- timeout = createTimeout();
- });
- observer.observe(window.document.body, { childList: true, subtree: true });
- });
-}
-
-export function calculateViewportHeight() {
- return Math.ceil(window.innerHeight * 0.75);
-}
-
-/**
- * Tests if the element actually responds to .scrollTo(...)
- * and that scrollTop changes as expected.
- */
-export function canElementScroll(elem: HTMLElement): boolean {
- // Quick check if scrollTo is a function
- if (typeof elem.scrollTo !== "function") {
- console.warn("canElementScroll: .scrollTo is not a function.");
- return false;
- }
-
- try {
- const originalTop = elem.scrollTop;
-
- // try to scroll
- elem.scrollTo({
- top: originalTop + 100,
- left: 0,
- behavior: "instant",
- });
-
- // If scrollTop never changed, consider it unscrollable
- if (elem.scrollTop === originalTop) {
- throw new StagehandDomProcessError("scrollTop did not change");
- }
-
- // Scroll back to original place
- elem.scrollTo({
- top: originalTop,
- left: 0,
- behavior: "instant",
- });
-
- return true;
- } catch (error) {
- console.warn("canElementScroll error:", (error as Error).message || error);
- return false;
- }
-}
-
-export function getNodeFromXpath(xpath: string) {
- return document.evaluate(
- xpath,
- document.documentElement,
- null,
- XPathResult.FIRST_ORDERED_NODE_TYPE,
- null,
- ).singleNodeValue;
-}
-
-export function waitForElementScrollEnd(
- element: HTMLElement,
- idleMs = 100,
-): Promise {
- return new Promise((resolve) => {
- let scrollEndTimer: number | undefined;
-
- const handleScroll = () => {
- clearTimeout(scrollEndTimer);
- scrollEndTimer = window.setTimeout(() => {
- element.removeEventListener("scroll", handleScroll);
- resolve();
- }, idleMs);
- };
-
- element.addEventListener("scroll", handleScroll, { passive: true });
- handleScroll();
- });
-}
diff --git a/lib/dom/xpathUtils.ts b/lib/dom/xpathUtils.ts
deleted file mode 100644
index f08eb7479..000000000
--- a/lib/dom/xpathUtils.ts
+++ /dev/null
@@ -1,243 +0,0 @@
-import { isElementNode, isTextNode } from "./elementCheckUtils";
-
-function getParentElement(node: ChildNode): Element | null {
- return isElementNode(node)
- ? node.parentElement
- : (node.parentNode as Element);
-}
-
-/**
- * Generates all possible combinations of a given array of attributes.
- * @param attributes Array of attributes.
- * @param size The size of each combination.
- * @returns An array of attribute combinations.
- */
-function getCombinations(
- attributes: { attr: string; value: string }[],
- size: number,
-): { attr: string; value: string }[][] {
- const results: { attr: string; value: string }[][] = [];
-
- function helper(start: number, combo: { attr: string; value: string }[]) {
- if (combo.length === size) {
- results.push([...combo]);
- return;
- }
- for (let i = start; i < attributes.length; i++) {
- combo.push(attributes[i]);
- helper(i + 1, combo);
- combo.pop();
- }
- }
-
- helper(0, []);
- return results;
-}
-
-/**
- * Checks if the generated XPath uniquely identifies the target element.
- * @param xpath The XPath string to test.
- * @param target The target DOM element.
- * @returns True if unique, else false.
- */
-function isXPathFirstResultElement(xpath: string, target: Element): boolean {
- try {
- const result = document.evaluate(
- xpath,
- document.documentElement,
- null,
- XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
- null,
- );
- return result.snapshotItem(0) === target;
- } catch (error) {
- // If there's an error evaluating the XPath, consider it not unique
- console.warn(`Invalid XPath expression: ${xpath}`, error);
- return false;
- }
-}
-
-/**
- * Escapes a string for use in an XPath expression.
- * Handles special characters, including single and double quotes.
- * @param value - The string to escape.
- * @returns The escaped string safe for XPath.
- */
-export function escapeXPathString(value: string): string {
- if (value.includes("'")) {
- if (value.includes('"')) {
- // If the value contains both single and double quotes, split into parts
- return (
- "concat(" +
- value
- .split(/('+)/)
- .map((part) => {
- if (part === "'") {
- return `"'"`;
- } else if (part.startsWith("'") && part.endsWith("'")) {
- return `"${part}"`;
- } else {
- return `'${part}'`;
- }
- })
- .join(",") +
- ")"
- );
- } else {
- // Contains single quotes but not double quotes; use double quotes
- return `"${value}"`;
- }
- } else {
- // Does not contain single quotes; use single quotes
- return `'${value}'`;
- }
-}
-
-/**
- * Generates both a complicated XPath and a standard XPath for a given DOM element.
- * @param element - The target DOM element.
- * @param documentOverride - Optional document override.
- * @returns An object containing both XPaths.
- */
-export async function generateXPathsForElement(
- element: ChildNode,
-): Promise {
- // Generate the standard XPath
- if (!element) return [];
- const [complexXPath, standardXPath, idBasedXPath] = await Promise.all([
- generateComplexXPath(element),
- generateStandardXPath(element),
- generatedIdBasedXPath(element),
- ]);
-
- // This should return in order from most accurate on current page to most cachable.
- return [standardXPath, ...(idBasedXPath ? [idBasedXPath] : []), complexXPath];
-}
-
-async function generateComplexXPath(element: ChildNode): Promise {
- // Generate the complicated XPath
- const parts: string[] = [];
- let currentElement: ChildNode | null = element;
-
- while (
- currentElement &&
- (isTextNode(currentElement) || isElementNode(currentElement))
- ) {
- if (isElementNode(currentElement)) {
- const el = currentElement as Element;
- let selector = el.tagName.toLowerCase();
-
- // List of attributes to consider for uniqueness
- const attributePriority = [
- "data-qa",
- "data-component",
- "data-role",
- "role",
- "aria-role",
- "type",
- "name",
- "aria-label",
- "placeholder",
- "title",
- "alt",
- ];
-
- // Collect attributes present on the element
- const attributes = attributePriority
- .map((attr) => {
- let value = el.getAttribute(attr);
- if (attr === "href-full" && value) {
- value = el.getAttribute("href");
- }
- return value
- ? { attr: attr === "href-full" ? "href" : attr, value }
- : null;
- })
- .filter((attr) => attr !== null) as { attr: string; value: string }[];
-
- // Attempt to find a combination of attributes that uniquely identifies the element
- let uniqueSelector = "";
- for (let i = 1; i <= attributes.length; i++) {
- const combinations = getCombinations(attributes, i);
- for (const combo of combinations) {
- const conditions = combo
- .map((a) => `@${a.attr}=${escapeXPathString(a.value)}`)
- .join(" and ");
- const xpath = `//${selector}[${conditions}]`;
- if (isXPathFirstResultElement(xpath, el)) {
- uniqueSelector = xpath;
- break;
- }
- }
- if (uniqueSelector) break;
- }
-
- if (uniqueSelector) {
- parts.unshift(uniqueSelector.replace("//", ""));
- break;
- } else {
- // Fallback to positional selector
- const parent = getParentElement(el);
- if (parent) {
- const siblings = Array.from(parent.children).filter(
- (sibling) => sibling.tagName === el.tagName,
- );
- const index = siblings.indexOf(el as HTMLElement) + 1;
- selector += siblings.length > 1 ? `[${index}]` : "";
- }
- parts.unshift(selector);
- }
- }
-
- currentElement = getParentElement(currentElement);
- }
-
- const xpath = "//" + parts.join("/");
- return xpath;
-}
-
-/**
- * Generates a standard XPath for a given DOM element.
- * @param element - The target DOM element.
- * @returns A standard XPath string.
- */
-async function generateStandardXPath(element: ChildNode): Promise {
- const parts: string[] = [];
- while (element && (isTextNode(element) || isElementNode(element))) {
- let index = 0;
- let hasSameTypeSiblings = false;
- const siblings = element.parentElement
- ? Array.from(element.parentElement.childNodes)
- : [];
- for (let i = 0; i < siblings.length; i++) {
- const sibling = siblings[i];
- if (
- sibling.nodeType === element.nodeType &&
- sibling.nodeName === element.nodeName
- ) {
- index = index + 1;
- hasSameTypeSiblings = true;
- if (sibling.isSameNode(element)) {
- break;
- }
- }
- }
- // text "nodes" are selected differently than elements with xPaths
- if (element.nodeName !== "#text") {
- const tagName = element.nodeName.toLowerCase();
- const pathIndex = hasSameTypeSiblings ? `[${index}]` : "";
- parts.unshift(`${tagName}${pathIndex}`);
- }
- element = element.parentElement as HTMLElement;
- }
- return parts.length ? `/${parts.join("/")}` : "";
-}
-
-async function generatedIdBasedXPath(
- element: ChildNode,
-): Promise {
- if (isElementNode(element) && element.id) {
- return `//*[@id='${element.id}']`;
- }
- return null;
-}
diff --git a/lib/handlers/actHandler.ts b/lib/handlers/actHandler.ts
deleted file mode 100644
index d3442f07e..000000000
--- a/lib/handlers/actHandler.ts
+++ /dev/null
@@ -1,356 +0,0 @@
-import { Locator } from "@playwright/test";
-import { LogLine } from "../../types/log";
-import {
- PlaywrightCommandException,
- PlaywrightCommandMethodNotSupportedException,
-} from "../../types/playwright";
-import { LLMClient } from "../llm/LLMClient";
-import { StagehandPage } from "../StagehandPage";
-import {
- ActResult,
- ObserveResult,
- ActOptions,
- ObserveOptions,
-} from "@/types/stagehand";
-import { MethodHandlerContext, SupportedPlaywrightAction } from "@/types/act";
-import { buildActObservePrompt } from "../prompt";
-import {
- methodHandlerMap,
- fallbackLocatorMethod,
-} from "./handlerUtils/actHandlerUtils";
-import { StagehandObserveHandler } from "@/lib/handlers/observeHandler";
-import { StagehandInvalidArgumentError } from "@/types/stagehandErrors";
-/**
- * NOTE: Vision support has been removed from this version of Stagehand.
- * If useVision or verifierUseVision is set to true, a warning is logged and
- * the flow continues as if vision = false.
- */
-export class StagehandActHandler {
- private readonly stagehandPage: StagehandPage;
- private readonly logger: (logLine: LogLine) => void;
- private readonly selfHeal: boolean;
-
- constructor({
- logger,
- stagehandPage,
- selfHeal,
- }: {
- logger: (logLine: LogLine) => void;
- stagehandPage: StagehandPage;
- selfHeal: boolean;
- }) {
- this.logger = logger;
- this.stagehandPage = stagehandPage;
- this.selfHeal = selfHeal;
- }
-
- /**
- * Perform an immediate Playwright action based on an ObserveResult object
- * that was returned from `page.observe(...)`.
- */
- public async actFromObserveResult(
- observe: ObserveResult,
- domSettleTimeoutMs?: number,
- ): Promise {
- this.logger({
- category: "action",
- message: "Performing act from an ObserveResult",
- level: 1,
- auxiliary: {
- observeResult: {
- value: JSON.stringify(observe),
- type: "object",
- },
- },
- });
-
- const method = observe.method;
- if (method === "not-supported") {
- this.logger({
- category: "action",
- message: "Cannot execute ObserveResult with unsupported method",
- level: 1,
- auxiliary: {
- error: {
- value:
- "NotSupportedError: The method requested in this ObserveResult is not supported by Stagehand.",
- type: "string",
- },
- trace: {
- value: `Cannot execute act from ObserveResult with unsupported method: ${method}`,
- type: "string",
- },
- },
- });
- return {
- success: false,
- message: `Unable to perform action: The method '${method}' is not supported in ObserveResult. Please use a supported Playwright locator method.`,
- action: observe.description || `ObserveResult action (${method})`,
- };
- }
- const args = observe.arguments ?? [];
- // remove the xpath prefix on the selector
- const selector = observe.selector.replace("xpath=", "");
-
- try {
- await this._performPlaywrightMethod(
- method,
- args,
- selector,
- domSettleTimeoutMs,
- );
-
- return {
- success: true,
- message: `Action [${method}] performed successfully on selector: ${selector}`,
- action: observe.description || `ObserveResult action (${method})`,
- };
- } catch (err) {
- if (
- !this.selfHeal ||
- err instanceof PlaywrightCommandMethodNotSupportedException
- ) {
- this.logger({
- category: "action",
- message: "Error performing act from an ObserveResult",
- level: 1,
- auxiliary: {
- error: { value: err.message, type: "string" },
- trace: { value: err.stack, type: "string" },
- },
- });
- return {
- success: false,
- message: `Failed to perform act: ${err.message}`,
- action: observe.description || `ObserveResult action (${method})`,
- };
- }
- // We will try to use observeAct on a failed ObserveResult-act if selfHeal is true
- this.logger({
- category: "action",
- message:
- "Error performing act from an ObserveResult. Reprocessing the page and trying again",
- level: 1,
- auxiliary: {
- error: { value: err.message, type: "string" },
- trace: { value: err.stack, type: "string" },
- observeResult: { value: JSON.stringify(observe), type: "object" },
- },
- });
- try {
- // Remove redundancy from method-description
- const actCommand = observe.description
- .toLowerCase()
- .startsWith(method.toLowerCase())
- ? observe.description
- : method
- ? `${method} ${observe.description}`
- : observe.description;
- // Call act with the ObserveResult description
- return await this.stagehandPage.act({
- action: actCommand,
- });
- } catch (err) {
- this.logger({
- category: "action",
- message: "Error performing act from an ObserveResult on fallback",
- level: 1,
- auxiliary: {
- error: { value: err.message, type: "string" },
- trace: { value: err.stack, type: "string" },
- },
- });
- return {
- success: false,
- message: `Failed to perform act: ${err.message}`,
- action: observe.description || `ObserveResult action (${method})`,
- };
- }
- }
- }
-
- /**
- * Perform an act based on an instruction.
- * This method will observe the page and then perform the act on the first element returned.
- */
- public async observeAct(
- actionOrOptions: ActOptions,
- observeHandler: StagehandObserveHandler,
- llmClient: LLMClient,
- requestId: string,
- ): Promise {
- // Extract the action string
- let action: string;
- const observeOptions: Partial = {};
-
- if (typeof actionOrOptions === "object" && actionOrOptions !== null) {
- if (!("action" in actionOrOptions)) {
- throw new StagehandInvalidArgumentError(
- "Invalid argument. Action options must have an `action` field.",
- );
- }
-
- if (
- typeof actionOrOptions.action !== "string" ||
- actionOrOptions.action.length === 0
- ) {
- throw new StagehandInvalidArgumentError(
- "Invalid argument. No action provided.",
- );
- }
-
- action = actionOrOptions.action;
-
- // Extract options that should be passed to observe
- if (actionOrOptions.modelName)
- observeOptions.modelName = actionOrOptions.modelName;
- if (actionOrOptions.modelClientOptions)
- observeOptions.modelClientOptions = actionOrOptions.modelClientOptions;
- } else {
- throw new StagehandInvalidArgumentError(
- "Invalid argument. Valid arguments are: a string, an ActOptions object with an `action` field not empty, or an ObserveResult with a `selector` and `method` field.",
- );
- }
-
- // doObserveAndAct is just a wrapper of observeAct and actFromObserveResult.
- // we did this so that we can cleanly call a Promise.race, and race
- // doObserveAndAct against the user defined timeoutMs (if one was defined)
- const doObserveAndAct = async (): Promise => {
- const instruction = buildActObservePrompt(
- action,
- Object.values(SupportedPlaywrightAction),
- actionOrOptions.variables,
- );
-
- const observeResults = await observeHandler.observe({
- instruction,
- llmClient,
- requestId,
- onlyVisible: false,
- drawOverlay: false,
- returnAction: true,
- fromAct: true,
- });
-
- if (observeResults.length === 0) {
- return {
- success: false,
- message: `Failed to perform act: No observe results found for action`,
- action,
- };
- }
-
- const element: ObserveResult = observeResults[0];
-
- if (actionOrOptions.variables) {
- Object.keys(actionOrOptions.variables).forEach((key) => {
- element.arguments = element.arguments.map((arg) =>
- arg.replace(`%${key}%`, actionOrOptions.variables![key]),
- );
- });
- }
-
- return this.actFromObserveResult(
- element,
- actionOrOptions.domSettleTimeoutMs,
- );
- };
-
- // if no user defined timeoutMs, just do observeAct + actFromObserveResult
- // with no timeout
- if (!actionOrOptions.timeoutMs) {
- return doObserveAndAct();
- }
-
- // Race observeAct + actFromObserveResult vs. the timeoutMs
- const { timeoutMs } = actionOrOptions;
- return await Promise.race([
- doObserveAndAct(),
- new Promise((resolve) => {
- setTimeout(() => {
- resolve({
- success: false,
- message: `Action timed out after ${timeoutMs}ms`,
- action,
- });
- }, timeoutMs);
- }),
- ]);
- }
-
- private async _performPlaywrightMethod(
- method: string,
- args: unknown[],
- xpath: string,
- domSettleTimeoutMs?: number,
- ) {
- const locator = this.stagehandPage.page.locator(`xpath=${xpath}`).first();
- const initialUrl = this.stagehandPage.page.url();
-
- this.logger({
- category: "action",
- message: "performing playwright method",
- level: 2,
- auxiliary: {
- xpath: { value: xpath, type: "string" },
- method: { value: method, type: "string" },
- },
- });
-
- const context: MethodHandlerContext = {
- method,
- locator,
- xpath,
- args,
- logger: this.logger,
- stagehandPage: this.stagehandPage,
- initialUrl,
- domSettleTimeoutMs,
- };
-
- try {
- // 1) Look up a function in the map
- const methodFn = methodHandlerMap[method];
-
- // 2) If found, call it
- if (methodFn) {
- await methodFn(context);
-
- // 3) Otherwise, see if it's a valid locator method
- } else if (typeof locator[method as keyof Locator] === "function") {
- await fallbackLocatorMethod(context);
-
- // 4) If still unknown, we can’t handle it
- } else {
- this.logger({
- category: "action",
- message: "chosen method is invalid",
- level: 1,
- auxiliary: {
- method: { value: method, type: "string" },
- },
- });
- throw new PlaywrightCommandMethodNotSupportedException(
- `Method ${method} not supported`,
- );
- }
-
- // Always wait for DOM to settle
- await this.stagehandPage._waitForSettledDom(domSettleTimeoutMs);
- } catch (e) {
- this.logger({
- category: "action",
- message: "error performing method",
- level: 1,
- auxiliary: {
- error: { value: e.message, type: "string" },
- trace: { value: e.stack, type: "string" },
- method: { value: method, type: "string" },
- xpath: { value: xpath, type: "string" },
- args: { value: JSON.stringify(args), type: "object" },
- },
- });
- throw new PlaywrightCommandException(e.message);
- }
- }
-}
diff --git a/lib/handlers/agentHandler.ts b/lib/handlers/agentHandler.ts
deleted file mode 100644
index 3bb0e0227..000000000
--- a/lib/handlers/agentHandler.ts
+++ /dev/null
@@ -1,671 +0,0 @@
-import { StagehandPage } from "../StagehandPage";
-import { AgentProvider } from "../agent/AgentProvider";
-import { StagehandAgent } from "../agent/StagehandAgent";
-import { AgentClient } from "../agent/AgentClient";
-import { LogLine } from "../../types/log";
-import { Page } from "playwright";
-import {
- AgentExecuteOptions,
- AgentAction,
- AgentResult,
- AgentHandlerOptions,
- ActionExecutionResult,
-} from "@/types/agent";
-
-export class StagehandAgentHandler {
- private stagehandPage: StagehandPage;
- private agent: StagehandAgent;
- private provider: AgentProvider;
- private logger: (message: LogLine) => void;
- private agentClient: AgentClient;
- private options: AgentHandlerOptions;
-
- constructor(
- stagehandPage: StagehandPage,
- logger: (message: LogLine) => void,
- options: AgentHandlerOptions,
- ) {
- this.stagehandPage = stagehandPage;
- this.logger = logger;
- this.options = options;
-
- // Initialize the provider
- this.provider = new AgentProvider(logger);
-
- // Create client first
- const client = this.provider.getClient(
- options.modelName,
- options.clientOptions || {},
- options.userProvidedInstructions,
- );
-
- // Store the client
- this.agentClient = client;
-
- // Set up common functionality for any client type
- this.setupAgentClient();
-
- // Create agent with the client
- this.agent = new StagehandAgent(client, logger);
- }
-
- private setupAgentClient(): void {
- // Set up screenshot provider for any client type
- this.agentClient.setScreenshotProvider(async () => {
- const screenshot = await this.stagehandPage.page.screenshot({
- fullPage: false,
- });
- // Convert to base64
- return screenshot.toString("base64");
- });
-
- // Set up action handler for any client type
- this.agentClient.setActionHandler(async (action) => {
- // Default delay between actions (1 second if not specified)
- const defaultDelay = 1000;
- // Use specified delay or default
- const waitBetweenActions =
- (this.options.clientOptions?.waitBetweenActions as number) ||
- defaultDelay;
-
- try {
- // Try to inject cursor before each action
- try {
- await this.injectCursor();
- } catch {
- // Ignore cursor injection failures
- }
-
- // Add a small delay before the action for better visibility
- await new Promise((resolve) => setTimeout(resolve, 500));
-
- // Execute the action
- await this.executeAction(action);
-
- // Add a delay after the action for better visibility
- await new Promise((resolve) => setTimeout(resolve, waitBetweenActions));
-
- // After executing an action, take a screenshot
- try {
- await this.captureAndSendScreenshot();
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : String(error);
- this.logger({
- category: "agent",
- message: `Warning: Failed to take screenshot after action: ${errorMessage}. Continuing execution.`,
- level: 1,
- });
- // Continue execution even if screenshot fails
- }
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : String(error);
- this.logger({
- category: "agent",
- message: `Error executing action ${action.type}: ${errorMessage}`,
- level: 0,
- });
- throw error; // Re-throw the error to be handled by the caller
- }
- });
-
- // Update viewport and URL for any client type
- this.updateClientViewport();
- this.updateClientUrl();
- }
-
- /**
- * Execute a task with the agent
- */
- async execute(
- optionsOrInstruction: AgentExecuteOptions | string,
- ): Promise {
- const options =
- typeof optionsOrInstruction === "string"
- ? { instruction: optionsOrInstruction }
- : optionsOrInstruction;
-
- //Redirect to Google if the URL is empty or about:blank
- const currentUrl = this.stagehandPage.page.url();
- if (!currentUrl || currentUrl === "about:blank") {
- this.logger({
- category: "agent",
- message: `Page URL is empty or about:blank. Redirecting to www.google.com...`,
- level: 0,
- });
- await this.stagehandPage.page.goto("https://www.google.com");
- }
-
- this.logger({
- category: "agent",
- message: `Executing agent task: ${options.instruction}`,
- level: 1,
- });
-
- // Inject cursor for visual feedback
- try {
- await this.injectCursor();
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : String(error);
- this.logger({
- category: "agent",
- message: `Warning: Failed to inject cursor: ${errorMessage}. Continuing with execution.`,
- level: 1,
- });
- // Continue execution even if cursor injection fails
- }
-
- // Take initial screenshot if needed
- if (options.autoScreenshot !== false) {
- try {
- await this.captureAndSendScreenshot();
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : String(error);
- this.logger({
- category: "agent",
- message: `Warning: Failed to take initial screenshot: ${errorMessage}. Continuing with execution.`,
- level: 1,
- });
- // Continue execution even if screenshot fails
- }
- }
-
- // Execute the task
- const result = await this.agent.execute(optionsOrInstruction);
-
- // The actions are now executed during the agent's execution flow
- // We don't need to execute them again here
-
- return result;
- }
-
- /**
- * Execute a single action on the page
- */
- private async executeAction(
- action: AgentAction,
- ): Promise {
- try {
- switch (action.type) {
- case "click": {
- const { x, y, button = "left" } = action;
- // Update cursor position first
- await this.updateCursorPosition(x as number, y as number);
- // Animate the click
- await this.animateClick(x as number, y as number);
- // Small delay to see the animation
- await new Promise((resolve) => setTimeout(resolve, 300));
- // Perform the actual click
- await this.stagehandPage.page.mouse.click(x as number, y as number, {
- button: button as "left" | "right",
- });
- const newOpenedTab = await Promise.race([
- new Promise((resolve) => {
- this.stagehandPage.context.once("page", (page) => resolve(page));
- setTimeout(() => resolve(null), 1500);
- }),
- ]);
- if (newOpenedTab) {
- this.logger({
- category: "action",
- message: `New page detected (new tab) with URL. Opening on current page...`,
- level: 1,
- auxiliary: {
- url: {
- value: newOpenedTab.url(),
- type: "string",
- },
- },
- });
- await newOpenedTab.close();
- await this.stagehandPage.page.goto(newOpenedTab.url());
- await this.stagehandPage.page.waitForURL(newOpenedTab.url());
- }
- return { success: true };
- }
-
- case "double_click": {
- const { x, y } = action;
- // Update cursor position first
- await this.updateCursorPosition(x as number, y as number);
- // Animate the click
- await this.animateClick(x as number, y as number);
- // Small delay to see the animation
- await new Promise((resolve) => setTimeout(resolve, 200));
- // Animate the second click
- await this.animateClick(x as number, y as number);
- // Small delay to see the animation
- await new Promise((resolve) => setTimeout(resolve, 200));
- // Perform the actual double click
- await this.stagehandPage.page.mouse.dblclick(
- x as number,
- y as number,
- );
- return { success: true };
- }
-
- // Handle the case for "doubleClick" as well for backward compatibility
- case "doubleClick": {
- const { x, y } = action;
- // Update cursor position first
- await this.updateCursorPosition(x as number, y as number);
- // Animate the click
- await this.animateClick(x as number, y as number);
- // Small delay to see the animation
- await new Promise((resolve) => setTimeout(resolve, 200));
- // Animate the second click
- await this.animateClick(x as number, y as number);
- // Small delay to see the animation
- await new Promise((resolve) => setTimeout(resolve, 200));
- // Perform the actual double click
- await this.stagehandPage.page.mouse.dblclick(
- x as number,
- y as number,
- );
- return { success: true };
- }
-
- case "type": {
- const { text } = action;
- await this.stagehandPage.page.keyboard.type(text as string);
- return { success: true };
- }
-
- case "keypress": {
- const { keys } = action;
- if (Array.isArray(keys)) {
- for (const key of keys) {
- // Handle special keys
- if (key.includes("ENTER")) {
- await this.stagehandPage.page.keyboard.press("Enter");
- } else if (key.includes("SPACE")) {
- await this.stagehandPage.page.keyboard.press(" ");
- } else if (key.includes("TAB")) {
- await this.stagehandPage.page.keyboard.press("Tab");
- } else if (key.includes("ESCAPE") || key.includes("ESC")) {
- await this.stagehandPage.page.keyboard.press("Escape");
- } else if (key.includes("BACKSPACE")) {
- await this.stagehandPage.page.keyboard.press("Backspace");
- } else if (key.includes("DELETE")) {
- await this.stagehandPage.page.keyboard.press("Delete");
- } else if (key.includes("ARROW_UP")) {
- await this.stagehandPage.page.keyboard.press("ArrowUp");
- } else if (key.includes("ARROW_DOWN")) {
- await this.stagehandPage.page.keyboard.press("ArrowDown");
- } else if (key.includes("ARROW_LEFT")) {
- await this.stagehandPage.page.keyboard.press("ArrowLeft");
- } else if (key.includes("ARROW_RIGHT")) {
- await this.stagehandPage.page.keyboard.press("ArrowRight");
- } else {
- // For other keys, use the existing conversion
- const playwrightKey = this.convertKeyName(key);
- await this.stagehandPage.page.keyboard.press(playwrightKey);
- }
- }
- }
- return { success: true };
- }
-
- case "scroll": {
- const { x, y, scroll_x = 0, scroll_y = 0 } = action;
- // First move to the position
- await this.stagehandPage.page.mouse.move(x as number, y as number);
- // Then scroll
- await this.stagehandPage.page.evaluate(
- ({ scrollX, scrollY }) => window.scrollBy(scrollX, scrollY),
- { scrollX: scroll_x as number, scrollY: scroll_y as number },
- );
- return { success: true };
- }
-
- case "drag": {
- const { path } = action;
- if (Array.isArray(path) && path.length >= 2) {
- const start = path[0];
-
- // Update cursor position for start
- await this.updateCursorPosition(start.x, start.y);
- await this.stagehandPage.page.mouse.move(start.x, start.y);
- await this.stagehandPage.page.mouse.down();
-
- // Update cursor position for each point in the path
- for (let i = 1; i < path.length; i++) {
- await this.updateCursorPosition(path[i].x, path[i].y);
- await this.stagehandPage.page.mouse.move(path[i].x, path[i].y);
- }
-
- await this.stagehandPage.page.mouse.up();
- }
- return { success: true };
- }
-
- case "move": {
- const { x, y } = action;
- // Update cursor position first
- await this.updateCursorPosition(x as number, y as number);
- await this.stagehandPage.page.mouse.move(x as number, y as number);
- return { success: true };
- }
-
- case "wait": {
- await new Promise((resolve) => setTimeout(resolve, 1000));
- return { success: true };
- }
-
- case "screenshot": {
- // Screenshot is handled automatically by the agent client
- // after each action, so we don't need to do anything here
- return { success: true };
- }
-
- case "function": {
- const { name, arguments: args = {} } = action;
-
- if (
- name === "goto" &&
- typeof args === "object" &&
- args !== null &&
- "url" in args
- ) {
- await this.stagehandPage.page.goto(args.url as string);
- this.updateClientUrl();
- return { success: true };
- } else if (name === "back") {
- await this.stagehandPage.page.goBack();
- this.updateClientUrl();
- return { success: true };
- } else if (name === "forward") {
- await this.stagehandPage.page.goForward();
- this.updateClientUrl();
- return { success: true };
- } else if (name === "reload") {
- await this.stagehandPage.page.reload();
- this.updateClientUrl();
- return { success: true };
- }
-
- return {
- success: false,
- error: `Unsupported function: ${name}`,
- };
- }
-
- case "key": {
- // Handle the 'key' action type from Anthropic
- const { text } = action;
- if (text === "Return" || text === "Enter") {
- await this.stagehandPage.page.keyboard.press("Enter");
- } else if (text === "Tab") {
- await this.stagehandPage.page.keyboard.press("Tab");
- } else if (text === "Escape" || text === "Esc") {
- await this.stagehandPage.page.keyboard.press("Escape");
- } else if (text === "Backspace") {
- await this.stagehandPage.page.keyboard.press("Backspace");
- } else {
- // For other keys, try to press directly
- await this.stagehandPage.page.keyboard.press(text as string);
- }
- return { success: true };
- }
-
- default:
- return {
- success: false,
- error: `Unsupported action type: ${action.type}`,
- };
- }
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : String(error);
-
- this.logger({
- category: "agent",
- message: `Error executing action ${action.type}: ${errorMessage}`,
- level: 0,
- });
-
- return {
- success: false,
- error: errorMessage,
- };
- }
- }
-
- private updateClientViewport(): void {
- const viewportSize = this.stagehandPage.page.viewportSize();
- if (viewportSize) {
- this.agentClient.setViewport(viewportSize.width, viewportSize.height);
- }
- }
-
- private updateClientUrl(): void {
- const url = this.stagehandPage.page.url();
- this.agentClient.setCurrentUrl(url);
- }
-
- getAgent(): StagehandAgent {
- return this.agent;
- }
-
- getClient(): AgentClient {
- return this.agentClient;
- }
-
- async captureAndSendScreenshot(): Promise {
- this.logger({
- category: "agent",
- message: "Taking screenshot and sending to agent",
- level: 1,
- });
-
- try {
- // Take screenshot of the current page
- const screenshot = await this.stagehandPage.page.screenshot({
- type: "png",
- fullPage: false,
- });
-
- // Convert to base64
- const base64Image = screenshot.toString("base64");
-
- // Just use the captureScreenshot method on the agent client
- return await this.agentClient.captureScreenshot({
- base64Image,
- currentUrl: this.stagehandPage.page.url(),
- });
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : String(error);
- this.logger({
- category: "agent",
- message: `Error capturing screenshot: ${errorMessage}`,
- level: 0,
- });
- return null;
- }
- }
-
- /**
- * Inject a cursor element into the page for visual feedback
- */
- private async injectCursor(): Promise {
- try {
- // Define constants for cursor and highlight element IDs
- const CURSOR_ID = "stagehand-cursor";
- const HIGHLIGHT_ID = "stagehand-highlight";
-
- // Check if cursor already exists
- const cursorExists = await this.stagehandPage.page.evaluate(
- (id: string) => {
- return !!document.getElementById(id);
- },
- CURSOR_ID,
- );
-
- if (cursorExists) {
- return;
- }
-
- // Inject cursor and highlight elements
- await this.stagehandPage.page.evaluate(`
- (function(cursorId, highlightId) {
- // Create cursor element
- const cursor = document.createElement('div');
- cursor.id = cursorId;
-
- // Use the provided SVG for a custom cursor
- cursor.innerHTML = \`
-
-
-
-
- \`;
-
- // Style the cursor
- cursor.style.position = 'absolute';
- cursor.style.top = '0';
- cursor.style.left = '0';
- cursor.style.width = '28px';
- cursor.style.height = '28px';
- cursor.style.pointerEvents = 'none';
- cursor.style.zIndex = '9999999';
- cursor.style.transform = 'translate(-4px, -4px)'; // Adjust to align the pointer tip
-
- // Create highlight element for click animation
- const highlight = document.createElement('div');
- highlight.id = highlightId;
- highlight.style.position = 'absolute';
- highlight.style.width = '20px';
- highlight.style.height = '20px';
- highlight.style.borderRadius = '50%';
- highlight.style.backgroundColor = 'rgba(66, 134, 244, 0)';
- highlight.style.transform = 'translate(-50%, -50%) scale(0)';
- highlight.style.pointerEvents = 'none';
- highlight.style.zIndex = '9999998';
- highlight.style.transition = 'transform 0.3s ease-out, opacity 0.3s ease-out';
- highlight.style.opacity = '0';
-
- // Add elements to the document
- document.body.appendChild(cursor);
- document.body.appendChild(highlight);
-
- // Add a function to update cursor position
- window.__updateCursorPosition = function(x, y) {
- if (cursor) {
- cursor.style.transform = \`translate(\${x - 4}px, \${y - 4}px)\`;
- }
- };
-
- // Add a function to animate click
- window.__animateClick = function(x, y) {
- if (highlight) {
- highlight.style.left = \`\${x}px\`;
- highlight.style.top = \`\${y}px\`;
- highlight.style.transform = 'translate(-50%, -50%) scale(1)';
- highlight.style.opacity = '1';
-
- setTimeout(() => {
- highlight.style.transform = 'translate(-50%, -50%) scale(0)';
- highlight.style.opacity = '0';
- }, 300);
- }
- };
- })('${CURSOR_ID}', '${HIGHLIGHT_ID}');
- `);
-
- this.logger({
- category: "agent",
- message: "Cursor injected for visual feedback",
- level: 1,
- });
- } catch (error) {
- this.logger({
- category: "agent",
- message: `Failed to inject cursor: ${error}`,
- level: 0,
- });
- }
- }
-
- /**
- * Update the cursor position on the page
- */
- private async updateCursorPosition(x: number, y: number): Promise {
- try {
- await this.stagehandPage.page.evaluate(
- ({ x, y }) => {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- if ((window as any).__updateCursorPosition) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (window as any).__updateCursorPosition(x, y);
- }
- },
- { x, y },
- );
- } catch {
- // Silently fail if cursor update fails
- // This is not critical functionality
- }
- }
-
- /**
- * Animate a click at the given position
- */
- private async animateClick(x: number, y: number): Promise {
- try {
- await this.stagehandPage.page.evaluate(
- ({ x, y }) => {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- if ((window as any).__animateClick) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (window as any).__animateClick(x, y);
- }
- },
- { x, y },
- );
- } catch {
- // Silently fail if animation fails
- // This is not critical functionality
- }
- }
-
- private convertKeyName(key: string): string {
- // Map of CUA key names to Playwright key names
- const keyMap: Record = {
- ENTER: "Enter",
- ESCAPE: "Escape",
- BACKSPACE: "Backspace",
- TAB: "Tab",
- SPACE: " ",
- ARROWUP: "ArrowUp",
- ARROWDOWN: "ArrowDown",
- ARROWLEFT: "ArrowLeft",
- ARROWRIGHT: "ArrowRight",
- UP: "ArrowUp",
- DOWN: "ArrowDown",
- LEFT: "ArrowLeft",
- RIGHT: "ArrowRight",
- SHIFT: "Shift",
- CONTROL: "Control",
- ALT: "Alt",
- META: "Meta",
- COMMAND: "Meta",
- CMD: "Meta",
- CTRL: "Control",
- DELETE: "Delete",
- HOME: "Home",
- END: "End",
- PAGEUP: "PageUp",
- PAGEDOWN: "PageDown",
- };
-
- // Convert to uppercase for case-insensitive matching
- const upperKey = key.toUpperCase();
-
- // Return the mapped key or the original key if not found
- return keyMap[upperKey] || key;
- }
-}
diff --git a/lib/handlers/extractHandler.ts b/lib/handlers/extractHandler.ts
deleted file mode 100644
index 085d09c79..000000000
--- a/lib/handlers/extractHandler.ts
+++ /dev/null
@@ -1,896 +0,0 @@
-import { z, ZodTypeAny } from "zod";
-import { LogLine } from "../../types/log";
-import { ZodPathSegments } from "../../types/stagehand";
-import { TextAnnotation } from "../../types/textannotation";
-import { extract } from "../inference";
-import { LLMClient } from "../llm/LLMClient";
-import { formatText } from "../utils";
-import { StagehandPage } from "../StagehandPage";
-import { Stagehand, StagehandFunctionName } from "../index";
-import { pageTextSchema } from "../../types/page";
-import { getAccessibilityTree } from "@/lib/a11y/utils";
-
-const PROXIMITY_THRESHOLD = 15;
-
-/**
- * The `StagehandExtractHandler` class is responsible for extracting structured data from a webpage.
- * It provides two approaches: `textExtract` and `domExtract`. `textExtract` is used by default.
- *
- * Here is what `textExtract` does at a high level:
- *
- * **1. Wait for the DOM to settle and start DOM debugging.**
- * - Ensures the page is fully loaded and stable before extraction.
- *
- * **2. Store the original DOM before any mutations.**
- * - Preserves the initial state of the DOM to restore later.
- * - We do this because creating spans around every word in the DOM (see step 4)
- * becomes very difficult to revert. Text nodes can be finicky, and directly
- * removing the added spans often corrupts the structure of the DOM.
- *
- * **3. Process the DOM to generate a selector map of candidate elements.**
- * - Identifies potential elements that contain the data to extract.
- *
- * **4. Create text bounding boxes around every word in the webpage.**
- * - Wraps words in spans so that their bounding boxes can be used to
- * determine their positions on the text-rendered-webpage.
- *
- * **5. Collect all text annotations (with positions and dimensions) from each of the candidate elements.**
- * - Gathers text and positional data for each word.
- *
- * **6. Group annotations by text and deduplicate them based on proximity.**
- * - There is no guarantee that the text annotations are unique (candidate elements can be nested).
- * - Thus, we must remove duplicate words that are close to each other on the page.
- *
- * **7. Restore the original DOM after mutations.**
- * - Returns the DOM to its original state after processing.
- *
- * **8. Format the deduplicated annotations into a text representation.**
- * - Prepares the text data for the extraction process.
- *
- * **9. Pass the formatted text to an LLM for extraction according to the given instruction and schema.**
- * - Uses a language model to extract structured data based on instructions.
- *
- * **10. Handle the extraction response and logging the results.**
- * - Processes the output from the LLM and logs relevant information.
- *
- * @remarks
- * Each step corresponds to specific code segments, as noted in the comments throughout the code.
- */
-
-export class StagehandExtractHandler {
- private readonly stagehand: Stagehand;
- private readonly stagehandPage: StagehandPage;
- private readonly logger: (logLine: LogLine) => void;
- private readonly userProvidedInstructions?: string;
-
- constructor({
- stagehand,
- logger,
- stagehandPage,
- userProvidedInstructions,
- }: {
- stagehand: Stagehand;
- logger: (message: {
- category?: string;
- message: string;
- level?: number;
- auxiliary?: { [key: string]: { value: string; type: string } };
- }) => void;
- stagehandPage: StagehandPage;
- userProvidedInstructions?: string;
- }) {
- this.stagehand = stagehand;
- this.logger = logger;
- this.stagehandPage = stagehandPage;
- this.userProvidedInstructions = userProvidedInstructions;
- }
-
- public async extract({
- instruction,
- schema,
- content = {},
- llmClient,
- requestId,
- domSettleTimeoutMs,
- useTextExtract = false,
- selector,
- }: {
- instruction?: string;
- schema?: T;
- content?: z.infer;
- chunksSeen?: Array;
- llmClient?: LLMClient;
- requestId?: string;
- domSettleTimeoutMs?: number;
- useTextExtract?: boolean;
- selector?: string;
- } = {}): Promise> {
- const noArgsCalled = !instruction && !schema && !llmClient && !selector;
- if (noArgsCalled) {
- this.logger({
- category: "extraction",
- message: "Extracting the entire page text.",
- level: 1,
- });
- return this.extractPageText();
- }
-
- if (useTextExtract) {
- return this.textExtract({
- instruction,
- schema,
- content,
- llmClient,
- requestId,
- domSettleTimeoutMs,
- selector,
- });
- } else {
- return this.domExtract({
- instruction,
- schema,
- content,
- llmClient,
- requestId,
- domSettleTimeoutMs,
- });
- }
- }
-
- private async extractPageText(): Promise<{ page_text?: string }> {
- await this.stagehandPage._waitForSettledDom();
-
- const originalDOM = await this.stagehandPage.page.evaluate(() =>
- window.storeDOM(undefined),
- );
-
- const { selectorMap }: { selectorMap: Record } =
- await this.stagehand.page.evaluate(() =>
- window.processAllOfDom(undefined),
- );
-
- await this.stagehand.page.evaluate(() =>
- window.createTextBoundingBoxes(undefined),
- );
-
- const containerDims = await this.getTargetDimensions();
-
- const allAnnotations = await this.collectAllAnnotations(
- selectorMap,
- containerDims.width,
- containerDims.height,
- containerDims.offsetLeft,
- containerDims.offsetTop,
- );
-
- const deduplicatedTextAnnotations =
- this.deduplicateAnnotations(allAnnotations);
-
- await this.stagehandPage.page.evaluate(
- (dom) => window.restoreDOM(dom, undefined),
- originalDOM,
- );
-
- const formattedText = formatText(
- deduplicatedTextAnnotations,
- containerDims.width,
- );
-
- const result = { page_text: formattedText };
- return pageTextSchema.parse(result);
- }
-
- private async textExtract({
- instruction,
- schema,
- content = {},
- llmClient,
- requestId,
- domSettleTimeoutMs,
- selector,
- }: {
- instruction?: string;
- schema?: T;
- content?: z.infer;
- llmClient?: LLMClient;
- requestId?: string;
- domSettleTimeoutMs?: number;
- selector?: string;
- }): Promise> {
- this.logger({
- category: "extraction",
- message: "starting extraction",
- level: 1,
- auxiliary: {
- instruction: {
- value: instruction,
- type: "string",
- },
- },
- });
-
- // **1:** Wait for the DOM to settle and start DOM debugging
- await this.stagehandPage._waitForSettledDom(domSettleTimeoutMs);
-
- const targetXpath = selector?.replace(/^xpath=/, "") ?? "";
-
- // **2:** Store the original DOM before any mutations
- // we need to store the original DOM here because calling createTextBoundingBoxes()
- // will mutate the DOM by adding spans around every word
- const originalDOM = await this.stagehandPage.page.evaluate(
- (xp) => window.storeDOM(xp),
- targetXpath,
- );
-
- // **3:** Process the DOM to generate a selector map of candidate elements
- const { selectorMap }: { selectorMap: Record } =
- await this.stagehand.page.evaluate(
- (xp) => window.processAllOfDom(xp),
- targetXpath,
- );
-
- this.logger({
- category: "extraction",
- message: `received output from processAllOfDom. selectorMap has ${
- Object.keys(selectorMap).length
- } entries`,
- level: 1,
- });
-
- // **4:** Create text bounding boxes around every word in the webpage
- // calling createTextBoundingBoxes() will create a span around every word on the
- // webpage. The bounding boxes of these spans will be used to determine their
- // positions in the text rendered webpage
- await this.stagehand.page.evaluate(
- (xp) => window.createTextBoundingBoxes(xp),
- targetXpath,
- );
-
- // **5:** Determine the container dimensions for either the entire page or the target element
- const {
- width: containerWidth,
- height: containerHeight,
- offsetLeft = 0,
- offsetTop = 0,
- } = await this.getTargetDimensions(targetXpath);
-
- // **6:** Collect all text annotations (with positions and dimensions) from the candidate elements
- // allAnnotations will store all the TextAnnotations BEFORE deduplication
- const allAnnotations = await this.collectAllAnnotations(
- selectorMap,
- containerWidth,
- containerHeight,
- offsetLeft,
- offsetTop,
- );
-
- // **7:** Group annotations by text and deduplicate them based on proximity
- const annotationsGroupedByText = new Map();
-
- for (const annotation of allAnnotations) {
- if (!annotationsGroupedByText.has(annotation.text)) {
- annotationsGroupedByText.set(annotation.text, []);
- }
- annotationsGroupedByText.get(annotation.text)!.push(annotation);
- }
-
- const deduplicatedTextAnnotations: TextAnnotation[] = [];
-
- // here, we deduplicate annotations per text group
- for (const [text, annotations] of annotationsGroupedByText.entries()) {
- for (const annotation of annotations) {
- // check if this annotation is close to any existing deduplicated annotation
- const isDuplicate = deduplicatedTextAnnotations.some(
- (existingAnnotation) => {
- if (existingAnnotation.text !== text) return false;
-
- const dx =
- existingAnnotation.bottom_left.x - annotation.bottom_left.x;
- const dy =
- existingAnnotation.bottom_left.y - annotation.bottom_left.y;
- const distance = Math.hypot(dx, dy);
- // the annotation is a duplicate if it has the same text and its bottom_left
- // position is within the PROXIMITY_THRESHOLD of an existing annotation.
- // we calculate the Euclidean distance between the two bottom_left points,
- // and if the distance is less than PROXIMITY_THRESHOLD,
- // the annotation is considered a duplicate.
- return distance < PROXIMITY_THRESHOLD;
- },
- );
-
- if (!isDuplicate) {
- deduplicatedTextAnnotations.push(annotation);
- }
- }
- }
-
- // **8:** Restore the original DOM after mutations
- await this.stagehandPage.page.evaluate(
- ({ dom, xp }) => window.restoreDOM(dom, xp),
- { dom: originalDOM, xp: targetXpath },
- );
-
- // **9:** Format the deduplicated annotations into a text representation
- const formattedText = formatText(
- deduplicatedTextAnnotations,
- containerWidth,
- );
-
- // **10:** Pass the formatted text to an LLM for extraction according to the given instruction and schema
- const extractionResponse = await extract({
- instruction,
- previouslyExtractedContent: content,
- domElements: formattedText,
- schema,
- chunksSeen: 1,
- chunksTotal: 1,
- llmClient,
- requestId,
- userProvidedInstructions: this.userProvidedInstructions,
- logger: this.logger,
- logInferenceToFile: this.stagehand.logInferenceToFile,
- });
-
- const {
- metadata: { completed },
- prompt_tokens: promptTokens,
- completion_tokens: completionTokens,
- inference_time_ms: inferenceTimeMs,
- ...output
- } = extractionResponse;
-
- this.stagehand.updateMetrics(
- StagehandFunctionName.EXTRACT,
- promptTokens,
- completionTokens,
- inferenceTimeMs,
- );
-
- // **11:** Handle the extraction response and log the results
- this.logger({
- category: "extraction",
- message: "received extraction response",
- auxiliary: {
- extraction_response: {
- value: JSON.stringify(extractionResponse),
- type: "object",
- },
- },
- });
-
- if (completed) {
- this.logger({
- category: "extraction",
- message: "extraction completed successfully",
- level: 1,
- auxiliary: {
- extraction_response: {
- value: JSON.stringify(extractionResponse),
- type: "object",
- },
- },
- });
- } else {
- this.logger({
- category: "extraction",
- message: "extraction incomplete after processing all data",
- level: 1,
- auxiliary: {
- extraction_response: {
- value: JSON.stringify(extractionResponse),
- type: "object",
- },
- },
- });
- }
- return output;
- }
-
- private async domExtract({
- instruction,
- schema,
- content = {},
- llmClient,
- requestId,
- domSettleTimeoutMs,
- }: {
- instruction: string;
- schema: T;
- content?: z.infer;
- llmClient: LLMClient;
- requestId?: string;
- domSettleTimeoutMs?: number;
- }): Promise> {
- this.logger({
- category: "extraction",
- message: "starting extraction using a11y tree",
- level: 1,
- auxiliary: {
- instruction: {
- value: instruction,
- type: "string",
- },
- },
- });
-
- await this.stagehandPage._waitForSettledDom(domSettleTimeoutMs);
- const tree = await getAccessibilityTree(this.stagehandPage, this.logger);
- this.logger({
- category: "extraction",
- message: "Getting accessibility tree data",
- level: 1,
- });
- const outputString = tree.simplified;
- const idToUrlMapping = tree.idToUrl;
-
- // Transform user defined schema to replace string().url() with .number()
- const [transformedSchema, urlFieldPaths] =
- transformUrlStringsToNumericIds(schema);
-
- // call extract inference with transformed schema
- const extractionResponse = await extract({
- instruction,
- previouslyExtractedContent: content,
- domElements: outputString,
- schema: transformedSchema,
- chunksSeen: 1,
- chunksTotal: 1,
- llmClient,
- requestId,
- userProvidedInstructions: this.userProvidedInstructions,
- logger: this.logger,
- logInferenceToFile: this.stagehand.logInferenceToFile,
- });
-
- const {
- metadata: { completed },
- prompt_tokens: promptTokens,
- completion_tokens: completionTokens,
- inference_time_ms: inferenceTimeMs,
- ...output
- } = extractionResponse;
-
- this.stagehand.updateMetrics(
- StagehandFunctionName.EXTRACT,
- promptTokens,
- completionTokens,
- inferenceTimeMs,
- );
-
- this.logger({
- category: "extraction",
- message: "received extraction response",
- auxiliary: {
- extraction_response: {
- value: JSON.stringify(extractionResponse),
- type: "object",
- },
- },
- });
-
- if (completed) {
- this.logger({
- category: "extraction",
- message: "extraction completed successfully",
- level: 1,
- auxiliary: {
- extraction_response: {
- value: JSON.stringify(extractionResponse),
- type: "object",
- },
- },
- });
- } else {
- this.logger({
- category: "extraction",
- message: "extraction incomplete after processing all data",
- level: 1,
- auxiliary: {
- extraction_response: {
- value: JSON.stringify(extractionResponse),
- type: "object",
- },
- },
- });
- }
-
- // revert to original schema and populate with URLs
- for (const { segments } of urlFieldPaths) {
- injectUrls(output, segments, idToUrlMapping);
- }
-
- return output as z.infer;
- }
- /**
- * Get the width, height, and offsets of either the entire page or a specific element.
- * (Matches your existing getTargetDimensions logic, just adapted to accept a string | undefined.)
- */
- private async getTargetDimensions(targetXpath?: string): Promise<{
- width: number;
- height: number;
- offsetLeft: number;
- offsetTop: number;
- }> {
- // If targetXpath is undefined, get entire page dimensions
- if (!targetXpath) {
- const { innerWidth, innerHeight } = await this.stagehand.page.evaluate(
- () => ({
- innerWidth: window.innerWidth,
- innerHeight: window.innerHeight,
- }),
- );
- return {
- width: innerWidth,
- height: innerHeight,
- offsetLeft: 0,
- offsetTop: 0,
- };
- }
-
- // If targetXpath is present, get element-specific dimensions
- const { elemWidth, elemHeight, offsetLeft, offsetTop } =
- await this.stagehand.page.evaluate((xp) => {
- const el = window.getNodeFromXpath(xp) as HTMLElement | null;
-
- if (!el) {
- return {
- elemWidth: window.innerWidth,
- elemHeight: window.innerHeight,
- offsetLeft: 0,
- offsetTop: 0,
- };
- }
-
- const rect = el.getBoundingClientRect();
- return {
- elemWidth: rect.width,
- elemHeight: rect.height,
- offsetLeft: rect.left,
- offsetTop: rect.top,
- };
- }, targetXpath);
-
- return {
- width: elemWidth,
- height: elemHeight,
- offsetLeft,
- offsetTop,
- };
- }
-
- /**
- * Collects the bounding boxes for each word inside each of the candidate element in selectorMap,
- * adjusting for container offsets, and producing an array of TextAnnotations.
- */
- private async collectAllAnnotations(
- selectorMap: Record,
- containerWidth: number,
- containerHeight: number,
- offsetLeft: number,
- offsetTop: number,
- ): Promise {
- const allAnnotations: TextAnnotation[] = [];
-
- // Loop over the candidate XPaths in the selector map
- for (const xpaths of Object.values(selectorMap)) {
- const xpath = xpaths[0];
-
- // Evaluate in the browser to get bounding boxes
- const boundingBoxes: Array<{
- text: string;
- left: number;
- top: number;
- width: number;
- height: number;
- }> = await this.stagehandPage.page.evaluate(
- (xp) => window.getElementBoundingBoxes(xp),
- xpath,
- );
-
- for (const box of boundingBoxes) {
- // 1. Subtract container offsets to get local coordinates
- const localLeft = box.left - offsetLeft;
- const localTop = box.top - offsetTop;
-
- // 2. bottom_left is local x, plus local y + height
- // so the baseline is at the bottom edge of the box
- const bottom_left = { x: localLeft, y: localTop + box.height };
-
- // 3. Normalize by dividing local positions by container width/height
- const bottom_left_normalized = {
- x: localLeft / containerWidth,
- y: (localTop + box.height) / containerHeight,
- };
-
- if (box.text.trim().length > 0) {
- allAnnotations.push({
- text: box.text,
- bottom_left,
- bottom_left_normalized,
- width: box.width,
- height: box.height,
- });
- }
- }
- }
-
- return allAnnotations;
- }
-
- /**
- * Deduplicate text annotations by grouping them by text, then removing duplicates
- * within a certain proximity threshold.
- */
- private deduplicateAnnotations(
- annotations: TextAnnotation[],
- ): TextAnnotation[] {
- const annotationsGroupedByText = new Map();
- const deduplicated: TextAnnotation[] = [];
-
- for (const annotation of annotations) {
- if (!annotationsGroupedByText.has(annotation.text)) {
- annotationsGroupedByText.set(annotation.text, []);
- }
- annotationsGroupedByText.get(annotation.text)!.push(annotation);
- }
-
- for (const [text, group] of annotationsGroupedByText.entries()) {
- for (const annotation of group) {
- const isDuplicate = deduplicated.some((existing) => {
- if (existing.text !== text) return false;
-
- const dx = existing.bottom_left.x - annotation.bottom_left.x;
- const dy = existing.bottom_left.y - annotation.bottom_left.y;
- const distance = Math.hypot(dx, dy);
- return distance < PROXIMITY_THRESHOLD;
- });
-
- if (!isDuplicate) {
- deduplicated.push(annotation);
- }
- }
- }
-
- return deduplicated;
- }
-}
-
-/**
- * Scans the provided Zod schema for any `z.string().url()` fields and
- * replaces them with `z.number()`.
- *
- * @param schema - The Zod object schema to transform.
- * @returns A tuple containing:
- * 1. The transformed schema (or the original schema if no changes were needed).
- * 2. An array of {@link ZodPathSegments} objects representing all the replaced URL fields,
- * with each path segment showing where in the schema the replacement occurred.
- */
-export function transformUrlStringsToNumericIds<
- T extends z.ZodObject,
->(schema: T): [T, ZodPathSegments[]] {
- const shape = schema._def.shape();
- const newShape: Record = {};
- const urlPaths: ZodPathSegments[] = [];
- let changed = false;
-
- for (const [key, value] of Object.entries(shape)) {
- const [childTransformed, childPaths] = transformSchema(value, [key]);
- newShape[key] = childTransformed;
- if (childTransformed !== value) {
- changed = true;
- }
- if (childPaths.length > 0) {
- childPaths.forEach((cp) => {
- urlPaths.push({ segments: [key, ...cp.segments] });
- });
- }
- }
-
- const finalSchema = changed ? z.object(newShape) : schema;
- return [finalSchema as T, urlPaths];
-}
-
-/**
- * Recursively traverses a given Zod schema, scanning for any fields of type `z.string().url()`.
- * For each such field, it replaces the `z.string().url()` with `z.number()`.
- *
- * This function is used internally by higher-level utilities (e.g., transforming entire object schemas)
- * and handles nested objects, arrays, unions, intersections, optionals.
- *
- * @param schema - The Zod schema to transform.
- * @param currentPath - An array of string/number keys representing the current schema path (used internally for recursion).
- * @returns A two-element tuple:
- * 1. The updated Zod schema, with any `.url()` fields replaced by `z.number()`.
- * 2. An array of {@link ZodPathSegments} objects representing each replaced field, including the path segments.
- */
-export function transformSchema(
- schema: ZodTypeAny,
- currentPath: Array,
-): [ZodTypeAny, ZodPathSegments[]] {
- // 1) If it's a string with .url(), convert to z.number()
- if (schema instanceof z.ZodString) {
- const hasUrlCheck =
- schema._def.checks?.some((check) => check.kind === "url") ?? false;
- if (hasUrlCheck) {
- return [
- z.number().describe("ID of element that points to a URL"),
- [{ segments: [] }],
- ];
- }
- return [schema, []];
- }
-
- // 2) If it's an object, transform each field
- if (schema instanceof z.ZodObject) {
- // The shape is a raw object containing fields keyed by string (no symbols):
- const shape = schema._def.shape() as Record;
- const newShape: Record = {};
- const urlPaths: ZodPathSegments[] = [];
- let changed = false;
-
- const shapeKeys = Object.keys(shape);
-
- for (const key of shapeKeys) {
- const child = shape[key];
- const [transformedChild, childPaths] = transformSchema(child, [
- ...currentPath,
- key,
- ]);
-
- if (transformedChild !== child) {
- changed = true;
- }
- newShape[key] = transformedChild;
-
- if (childPaths.length > 0) {
- for (const cp of childPaths) {
- urlPaths.push({ segments: [key, ...cp.segments] });
- }
- }
- }
-
- if (changed) {
- return [z.object(newShape), urlPaths];
- }
- return [schema, urlPaths];
- }
-
- // 3) If it's an array, transform its item type
- if (schema instanceof z.ZodArray) {
- const itemType = schema._def.type as ZodTypeAny;
- const [transformedItem, childPaths] = transformSchema(itemType, [
- ...currentPath,
- "*",
- ]);
- const changed = transformedItem !== itemType;
- const arrayPaths: ZodPathSegments[] = childPaths.map((cp) => ({
- segments: ["*", ...cp.segments],
- }));
-
- if (changed) {
- return [z.array(transformedItem), arrayPaths];
- }
- return [schema, arrayPaths];
- }
-
- // 4) If it's a union, transform each option
- if (schema instanceof z.ZodUnion) {
- // Cast the union’s options to an array of ZodTypeAny
- const unionOptions = schema._def.options as ZodTypeAny[];
- const newOptions: ZodTypeAny[] = [];
- let changed = false;
- let allPaths: ZodPathSegments[] = [];
-
- unionOptions.forEach((option: ZodTypeAny, idx: number) => {
- const [newOption, childPaths] = transformSchema(option, [
- ...currentPath,
- `union_${idx}`,
- ]);
- if (newOption !== option) {
- changed = true;
- }
- newOptions.push(newOption);
- allPaths = [...allPaths, ...childPaths];
- });
-
- if (changed) {
- // We assume at least two options remain:
- return [
- z.union(newOptions as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]),
- allPaths,
- ];
- }
- return [schema, allPaths];
- }
-
- // 5) If it's an intersection, transform left and right
- if (schema instanceof z.ZodIntersection) {
- const leftType = schema._def.left as ZodTypeAny;
- const rightType = schema._def.right as ZodTypeAny;
-
- const [left, leftPaths] = transformSchema(leftType, [
- ...currentPath,
- "intersection_left",
- ]);
- const [right, rightPaths] = transformSchema(rightType, [
- ...currentPath,
- "intersection_right",
- ]);
- const changed = left !== leftType || right !== rightType;
- const allPaths = [...leftPaths, ...rightPaths];
- if (changed) {
- return [z.intersection(left, right), allPaths];
- }
- return [schema, allPaths];
- }
-
- // 6) If it's optional, transform inner
- if (schema instanceof z.ZodOptional) {
- const innerType = schema._def.innerType as ZodTypeAny;
- const [inner, innerPaths] = transformSchema(innerType, currentPath);
- if (inner !== innerType) {
- return [z.optional(inner), innerPaths];
- }
- return [schema, innerPaths];
- }
-
- // 7) If it's nullable, transform inner
- if (schema instanceof z.ZodNullable) {
- const innerType = schema._def.innerType as ZodTypeAny;
- const [inner, innerPaths] = transformSchema(innerType, currentPath);
- if (inner !== innerType) {
- return [z.nullable(inner), innerPaths];
- }
- return [schema, innerPaths];
- }
-
- // 8) If it's an effect, transform base schema
- if (schema instanceof z.ZodEffects) {
- const baseSchema = schema._def.schema as ZodTypeAny;
- const [newBaseSchema, basePaths] = transformSchema(baseSchema, currentPath);
- if (newBaseSchema !== baseSchema) {
- return [z.effect(newBaseSchema, schema._def.effect), basePaths];
- }
- return [schema, basePaths];
- }
-
- // 9) If none of the above, return as-is
- return [schema, []];
-}
-
-/**
- * Once we get the final extracted object that has numeric IDs in place of URLs,
- * use `injectUrls` to walk the object and replace numeric IDs
- * with the real URL strings from idToUrlMapping. The `path` may include `*`
- * for array indices (indicating "all items in the array").
- */
-export function injectUrls(
- obj: unknown,
- path: Array,
- idToUrlMapping: Record,
-): void {
- if (path.length === 0) return;
- const [key, ...rest] = path;
-
- if (key === "*") {
- if (Array.isArray(obj)) {
- for (const item of obj) {
- injectUrls(item, rest, idToUrlMapping);
- }
- }
- return;
- }
-
- if (obj && typeof obj === "object") {
- const record = obj as Record;
- if (path.length === 1) {
- const fieldValue = record[key];
- if (typeof fieldValue === "number") {
- const mappedUrl = idToUrlMapping[String(fieldValue)];
- record[key] = mappedUrl ?? ``;
- }
- } else {
- injectUrls(record[key], rest, idToUrlMapping);
- }
- }
-}
diff --git a/lib/handlers/handlerUtils/actHandlerUtils.ts b/lib/handlers/handlerUtils/actHandlerUtils.ts
deleted file mode 100644
index 3cc41acc0..000000000
--- a/lib/handlers/handlerUtils/actHandlerUtils.ts
+++ /dev/null
@@ -1,570 +0,0 @@
-import { Page, Locator, errors as PlaywrightErrors } from "@playwright/test";
-import { PlaywrightCommandException } from "../../../types/playwright";
-import { StagehandPage } from "../../StagehandPage";
-import { getNodeFromXpath } from "@/lib/dom/utils";
-import { Logger } from "../../../types/log";
-import { MethodHandlerContext } from "@/types/act";
-
-/**
- * A mapping of playwright methods that may be chosen by the LLM to their
- * implementation.
- */
-export const methodHandlerMap: Record<
- string,
- (ctx: MethodHandlerContext) => Promise
-> = {
- scrollIntoView: scrollElementIntoView,
- scrollTo: scrollElementToPercentage,
- scroll: scrollElementToPercentage,
- "mouse.wheel": scrollElementToPercentage,
- fill: fillOrType,
- type: fillOrType,
- press: pressKey,
- click: clickElement,
- nextChunk: scrollToNextChunk,
- prevChunk: scrollToPreviousChunk,
-};
-
-export async function scrollToNextChunk(ctx: MethodHandlerContext) {
- const { stagehandPage, xpath, logger } = ctx;
-
- logger({
- category: "action",
- message: "scrolling to next chunk",
- level: 2,
- auxiliary: {
- xpath: { value: xpath, type: "string" },
- },
- });
-
- try {
- await stagehandPage.page.evaluate(
- ({ xpath }) => {
- const elementNode = getNodeFromXpath(xpath);
- if (!elementNode || elementNode.nodeType !== Node.ELEMENT_NODE) {
- console.warn(`Could not locate element to scroll by its height.`);
- return Promise.resolve();
- }
-
- const element = elementNode as HTMLElement;
- const tagName = element.tagName.toLowerCase();
- let height: number;
-
- if (tagName === "html" || tagName === "body") {
- height = window.visualViewport.height;
- window.scrollBy({
- top: height,
- left: 0,
- behavior: "smooth",
- });
-
- const scrollingEl =
- document.scrollingElement || document.documentElement;
- return window.waitForElementScrollEnd(scrollingEl as HTMLElement);
- } else {
- height = element.getBoundingClientRect().height;
- element.scrollBy({
- top: height,
- left: 0,
- behavior: "smooth",
- });
-
- return window.waitForElementScrollEnd(element);
- }
- },
- { xpath },
- );
- } catch (e) {
- logger({
- category: "action",
- message: "error scrolling to next chunk",
- level: 1,
- auxiliary: {
- error: { value: e.message, type: "string" },
- trace: { value: e.stack, type: "string" },
- xpath: { value: xpath, type: "string" },
- },
- });
- throw new PlaywrightCommandException(e.message);
- }
-}
-
-export async function scrollToPreviousChunk(ctx: MethodHandlerContext) {
- const { stagehandPage, xpath, logger } = ctx;
-
- logger({
- category: "action",
- message: "scrolling to previous chunk",
- level: 2,
- auxiliary: {
- xpath: { value: xpath, type: "string" },
- },
- });
-
- try {
- await stagehandPage.page.evaluate(
- ({ xpath }) => {
- const elementNode = getNodeFromXpath(xpath);
- if (!elementNode || elementNode.nodeType !== Node.ELEMENT_NODE) {
- console.warn(`Could not locate element to scroll by its height.`);
- return Promise.resolve();
- }
-
- const element = elementNode as HTMLElement;
- const tagName = element.tagName.toLowerCase();
- let height: number;
-
- if (tagName === "html" || tagName === "body") {
- height = window.visualViewport.height;
- window.scrollBy({
- top: -height,
- left: 0,
- behavior: "smooth",
- });
-
- const scrollingEl =
- document.scrollingElement || document.documentElement;
- return window.waitForElementScrollEnd(scrollingEl as HTMLElement);
- } else {
- height = element.getBoundingClientRect().height;
- element.scrollBy({
- top: -height,
- left: 0,
- behavior: "smooth",
- });
- return window.waitForElementScrollEnd(element);
- }
- },
- { xpath },
- );
- } catch (e) {
- logger({
- category: "action",
- message: "error scrolling to previous chunk",
- level: 1,
- auxiliary: {
- error: { value: e.message, type: "string" },
- trace: { value: e.stack, type: "string" },
- xpath: { value: xpath, type: "string" },
- },
- });
- throw new PlaywrightCommandException(e.message);
- }
-}
-
-export async function scrollElementIntoView(ctx: MethodHandlerContext) {
- const { locator, xpath, logger } = ctx;
-
- logger({
- category: "action",
- message: "scrolling element into view",
- level: 2,
- auxiliary: {
- xpath: { value: xpath, type: "string" },
- },
- });
-
- try {
- await locator.evaluate((element: HTMLElement) => {
- element.scrollIntoView({ behavior: "smooth", block: "center" });
- });
- } catch (e) {
- logger({
- category: "action",
- message: "error scrolling element into view",
- level: 1,
- auxiliary: {
- error: { value: e.message, type: "string" },
- trace: { value: e.stack, type: "string" },
- xpath: { value: xpath, type: "string" },
- },
- });
- throw new PlaywrightCommandException(e.message);
- }
-}
-
-export async function scrollElementToPercentage(ctx: MethodHandlerContext) {
- const { args, stagehandPage, xpath, logger } = ctx;
-
- logger({
- category: "action",
- message: "scrolling element vertically to specified percentage",
- level: 2,
- auxiliary: {
- xpath: { value: xpath, type: "string" },
- coordinate: { value: JSON.stringify(args), type: "string" },
- },
- });
-
- try {
- const [yArg = "0%"] = args as string[];
-
- await stagehandPage.page.evaluate(
- ({ xpath, yArg }) => {
- function parsePercent(val: string): number {
- const cleaned = val.trim().replace("%", "");
- const num = parseFloat(cleaned);
- return Number.isNaN(num) ? 0 : Math.max(0, Math.min(num, 100));
- }
-
- const elementNode = getNodeFromXpath(xpath);
- if (!elementNode || elementNode.nodeType !== Node.ELEMENT_NODE) {
- console.warn(`Could not locate element to scroll on.`);
- return;
- }
-
- const element = elementNode as HTMLElement;
- const yPct = parsePercent(yArg);
-
- if (element.tagName.toLowerCase() === "html") {
- const scrollHeight = document.body.scrollHeight;
- const viewportHeight = window.innerHeight;
- const scrollTop = (scrollHeight - viewportHeight) * (yPct / 100);
- window.scrollTo({
- top: scrollTop,
- left: window.scrollX,
- behavior: "smooth",
- });
- } else {
- const scrollHeight = element.scrollHeight;
- const clientHeight = element.clientHeight;
- const scrollTop = (scrollHeight - clientHeight) * (yPct / 100);
- element.scrollTo({
- top: scrollTop,
- left: element.scrollLeft,
- behavior: "smooth",
- });
- }
- },
- { xpath, yArg },
- );
- } catch (e) {
- logger({
- category: "action",
- message: "error scrolling element vertically to percentage",
- level: 1,
- auxiliary: {
- error: { value: e.message, type: "string" },
- trace: { value: e.stack, type: "string" },
- xpath: { value: xpath, type: "string" },
- args: { value: JSON.stringify(args), type: "object" },
- },
- });
- throw new PlaywrightCommandException(e.message);
- }
-}
-
-export async function fillOrType(ctx: MethodHandlerContext) {
- const { locator, xpath, args, logger } = ctx;
-
- try {
- await locator.fill("");
- await locator.click();
-
- const text = args[0]?.toString() || "";
- for (const char of text) {
- await locator.page().keyboard.type(char, {
- delay: Math.random() * 50 + 25,
- });
- }
- } catch (e) {
- logger({
- category: "action",
- message: "error filling element",
- level: 1,
- auxiliary: {
- error: { value: e.message, type: "string" },
- trace: { value: e.stack, type: "string" },
- xpath: { value: xpath, type: "string" },
- },
- });
- throw new PlaywrightCommandException(e.message);
- }
-}
-
-export async function pressKey(ctx: MethodHandlerContext) {
- const {
- locator,
- xpath,
- args,
- logger,
- stagehandPage,
- initialUrl,
- domSettleTimeoutMs,
- } = ctx;
- try {
- const key = args[0]?.toString() ?? "";
- await locator.page().keyboard.press(key);
-
- await handlePossiblePageNavigation(
- "press",
- xpath,
- initialUrl,
- stagehandPage,
- logger,
- domSettleTimeoutMs,
- );
- } catch (e) {
- logger({
- category: "action",
- message: "error pressing key",
- level: 1,
- auxiliary: {
- error: { value: e.message, type: "string" },
- trace: { value: e.stack, type: "string" },
- key: { value: args[0]?.toString() ?? "unknown", type: "string" },
- },
- });
- throw new PlaywrightCommandException(e.message);
- }
-}
-
-export async function clickElement(ctx: MethodHandlerContext) {
- const {
- locator,
- xpath,
- args,
- logger,
- stagehandPage,
- initialUrl,
- domSettleTimeoutMs,
- } = ctx;
-
- logger({
- category: "action",
- message: "page URL before click",
- level: 2,
- auxiliary: {
- url: {
- value: stagehandPage.page.url(),
- type: "string",
- },
- },
- });
-
- try {
- // If it's a radio input, try to click its label
- const isRadio = await locator.evaluate((el) => {
- return el instanceof HTMLInputElement && el.type === "radio";
- });
-
- // Extract the click options (if any) from args[0]
- const clickArg = (args[0] ?? {}) as Record;
-
- // Decide which locator we actually want to click (for radio inputs, prefer label if present)
- let finalLocator = locator;
- if (isRadio) {
- const inputId = await locator.evaluate(
- (el) => (el as HTMLInputElement).id,
- );
- let labelLocator = null;
-
- if (inputId) {
- labelLocator = stagehandPage.page.locator(`label[for="${inputId}"]`);
- }
- if (!labelLocator || (await labelLocator.count()) < 1) {
- // Check ancestor
- labelLocator = stagehandPage.page
- .locator(`xpath=${xpath}/ancestor::label`)
- .first();
- }
- if ((await labelLocator.count()) < 1) {
- // Check sibling
- labelLocator = locator
- .locator("xpath=following-sibling::label")
- .first();
- if ((await labelLocator.count()) < 1) {
- labelLocator = locator
- .locator("xpath=preceding-sibling::label")
- .first();
- }
- }
-
- if ((await labelLocator.count()) > 0) {
- finalLocator = labelLocator;
- }
- }
-
- // Try clicking with a short (5s) timeout
- try {
- await finalLocator.click({
- ...clickArg,
- timeout: 5000,
- });
- } catch (error) {
- // If it's a TimeoutError, retry with force: true
- if (error instanceof PlaywrightErrors.TimeoutError) {
- logger({
- category: "action",
- message: "First click attempt timed out, retrying with force...",
- level: 2,
- });
- try {
- await finalLocator.click({
- ...clickArg,
- force: true,
- });
- } catch (forceError) {
- // If forced click also fails, throw a more descriptive error
- throw new PlaywrightCommandException(
- `Failed to click element at [${xpath}]. ` +
- `Timeout after 5s, then force-click also failed. ` +
- `Original timeout error: ${error.message}, ` +
- `Force-click error: ${forceError.message}`,
- );
- }
- } else {
- // Non-timeout error on the first click
- throw new PlaywrightCommandException(
- `Failed to click element at [${xpath}]. ` + `Error: ${error.message}`,
- );
- }
- }
- } catch (e) {
- logger({
- category: "action",
- message: "error performing click",
- level: 1,
- auxiliary: {
- error: { value: e.message, type: "string" },
- trace: { value: e.stack, type: "string" },
- xpath: { value: xpath, type: "string" },
- method: { value: "click", type: "string" },
- args: { value: JSON.stringify(args), type: "object" },
- },
- });
-
- throw new PlaywrightCommandException(
- `Could not complete click action at [${xpath}]. Reason: ${e.message}`,
- );
- }
-
- await handlePossiblePageNavigation(
- "click",
- xpath,
- initialUrl,
- stagehandPage,
- logger,
- domSettleTimeoutMs,
- );
-}
-
-/**
- * Fallback method: if method is not in our map but *is* a valid Playwright locator method.
- */
-export async function fallbackLocatorMethod(ctx: MethodHandlerContext) {
- const { locator, xpath, method, args, logger } = ctx;
-
- logger({
- category: "action",
- message: "page URL before action",
- level: 2,
- auxiliary: {
- url: { value: locator.page().url(), type: "string" },
- },
- });
-
- try {
- await (
- locator[method as keyof Locator] as unknown as (
- ...a: string[]
- ) => Promise
- )(...args.map((arg) => arg?.toString() || ""));
- } catch (e) {
- logger({
- category: "action",
- message: "error performing method",
- level: 1,
- auxiliary: {
- error: { value: e.message, type: "string" },
- trace: { value: e.stack, type: "string" },
- xpath: { value: xpath, type: "string" },
- method: { value: method, type: "string" },
- args: { value: JSON.stringify(args), type: "object" },
- },
- });
- throw new PlaywrightCommandException(e.message);
- }
-}
-
-async function handlePossiblePageNavigation(
- actionDescription: string,
- xpath: string,
- initialUrl: string,
- stagehandPage: StagehandPage,
- logger: Logger,
- domSettleTimeoutMs?: number,
-): Promise {
- logger({
- category: "action",
- message: `${actionDescription}, checking for page navigation`,
- level: 1,
- auxiliary: {
- xpath: { value: xpath, type: "string" },
- },
- });
-
- const newOpenedTab = await Promise.race([
- new Promise((resolve) => {
- stagehandPage.context.once("page", (page) => resolve(page));
- setTimeout(() => resolve(null), 1500);
- }),
- ]);
-
- logger({
- category: "action",
- message: `${actionDescription} complete`,
- level: 1,
- auxiliary: {
- newOpenedTab: {
- value: newOpenedTab ? "opened a new tab" : "no new tabs opened",
- type: "string",
- },
- },
- });
-
- if (newOpenedTab) {
- logger({
- category: "action",
- message: "new page detected (new tab) with URL",
- level: 1,
- auxiliary: {
- url: { value: newOpenedTab.url(), type: "string" },
- },
- });
- await newOpenedTab.close();
- await stagehandPage.page.goto(newOpenedTab.url());
- await stagehandPage.page.waitForLoadState("domcontentloaded");
- }
-
- try {
- await stagehandPage._waitForSettledDom(domSettleTimeoutMs);
- } catch (e) {
- logger({
- category: "action",
- message: "wait for settled DOM timeout hit",
- level: 1,
- auxiliary: {
- trace: { value: e.stack, type: "string" },
- message: { value: e.message, type: "string" },
- },
- });
- }
-
- logger({
- category: "action",
- message: "finished waiting for (possible) page navigation",
- level: 1,
- });
-
- if (stagehandPage.page.url() !== initialUrl) {
- logger({
- category: "action",
- message: "new page detected with URL",
- level: 1,
- auxiliary: {
- url: { value: stagehandPage.page.url(), type: "string" },
- },
- });
- }
-}
diff --git a/lib/handlers/observeHandler.ts b/lib/handlers/observeHandler.ts
deleted file mode 100644
index b1e919c5c..000000000
--- a/lib/handlers/observeHandler.ts
+++ /dev/null
@@ -1,225 +0,0 @@
-import { LogLine } from "../../types/log";
-import { Stagehand, StagehandFunctionName } from "../index";
-import { observe } from "../inference";
-import { LLMClient } from "../llm/LLMClient";
-import { StagehandPage } from "../StagehandPage";
-import { generateId, drawObserveOverlay } from "../utils";
-import {
- getAccessibilityTree,
- getXPathByResolvedObjectId,
-} from "../a11y/utils";
-import { AccessibilityNode } from "../../types/context";
-
-export class StagehandObserveHandler {
- private readonly stagehand: Stagehand;
- private readonly logger: (logLine: LogLine) => void;
- private readonly stagehandPage: StagehandPage;
- private observations: {
- [key: string]: {
- result: { selector: string; description: string }[];
- instruction: string;
- };
- };
- private readonly userProvidedInstructions?: string;
- constructor({
- stagehand,
- logger,
- stagehandPage,
- userProvidedInstructions,
- }: {
- stagehand: Stagehand;
- logger: (logLine: LogLine) => void;
- stagehandPage: StagehandPage;
- userProvidedInstructions?: string;
- }) {
- this.stagehand = stagehand;
- this.logger = logger;
- this.stagehandPage = stagehandPage;
- this.userProvidedInstructions = userProvidedInstructions;
- this.observations = {};
- }
-
- private async _recordObservation(
- instruction: string,
- result: { selector: string; description: string }[],
- ): Promise {
- const id = generateId(instruction);
-
- this.observations[id] = { result, instruction };
-
- return id;
- }
-
- public async observe({
- instruction,
- llmClient,
- requestId,
- returnAction,
- onlyVisible,
- drawOverlay,
- fromAct,
- }: {
- instruction: string;
- llmClient: LLMClient;
- requestId: string;
- domSettleTimeoutMs?: number;
- returnAction?: boolean;
- onlyVisible?: boolean;
- drawOverlay?: boolean;
- fromAct?: boolean;
- }) {
- if (!instruction) {
- instruction = `Find elements that can be used for any future actions in the page. These may be navigation links, related pages, section/subsection links, buttons, or other interactive elements. Be comprehensive: if there are multiple elements that may be relevant for future actions, return all of them.`;
- }
-
- this.logger({
- category: "observation",
- message: "starting observation",
- level: 1,
- auxiliary: {
- instruction: {
- value: instruction,
- type: "string",
- },
- },
- });
-
- let selectorMap: Record = {};
- let outputString: string;
- let iframes: AccessibilityNode[] = [];
- const useAccessibilityTree = !onlyVisible;
- if (useAccessibilityTree) {
- await this.stagehandPage._waitForSettledDom();
- const tree = await getAccessibilityTree(this.stagehandPage, this.logger);
- this.logger({
- category: "observation",
- message: "Getting accessibility tree data",
- level: 1,
- });
- outputString = tree.simplified;
- iframes = tree.iframes;
- } else {
- const evalResult = await this.stagehand.page.evaluate(() => {
- return window.processAllOfDom().then((result) => result);
- });
- ({ outputString, selectorMap } = evalResult);
- }
-
- // No screenshot or vision-based annotation is performed
- const observationResponse = await observe({
- instruction,
- domElements: outputString,
- llmClient,
- requestId,
- userProvidedInstructions: this.userProvidedInstructions,
- logger: this.logger,
- isUsingAccessibilityTree: useAccessibilityTree,
- returnAction,
- logInferenceToFile: this.stagehand.logInferenceToFile,
- fromAct: fromAct,
- });
-
- const {
- prompt_tokens = 0,
- completion_tokens = 0,
- inference_time_ms = 0,
- } = observationResponse;
-
- this.stagehand.updateMetrics(
- fromAct ? StagehandFunctionName.ACT : StagehandFunctionName.OBSERVE,
- prompt_tokens,
- completion_tokens,
- inference_time_ms,
- );
-
- //Add iframes to the observation response if there are any on the page
- if (iframes.length > 0) {
- iframes.forEach((iframe) => {
- observationResponse.elements.push({
- elementId: Number(iframe.nodeId),
- description: "an iframe",
- method: "not-supported",
- arguments: [],
- });
- });
- }
- const elementsWithSelectors = await Promise.all(
- observationResponse.elements.map(async (element) => {
- const { elementId, ...rest } = element;
-
- if (useAccessibilityTree) {
- // Generate xpath for the given element if not found in selectorMap
- this.logger({
- category: "observation",
- message: "Getting xpath for element",
- level: 1,
- auxiliary: {
- elementId: {
- value: elementId.toString(),
- type: "string",
- },
- },
- });
-
- const args = { backendNodeId: elementId };
- const { object } = await this.stagehandPage.sendCDP<{
- object: { objectId: string };
- }>("DOM.resolveNode", args);
-
- if (!object || !object.objectId) {
- this.logger({
- category: "observation",
- message: `Invalid object ID returned for element: ${elementId}`,
- level: 1,
- });
- }
-
- const xpath = await getXPathByResolvedObjectId(
- await this.stagehandPage.getCDPClient(),
- object.objectId,
- );
-
- if (!xpath || xpath === "") {
- this.logger({
- category: "observation",
- message: `Empty xpath returned for element: ${elementId}`,
- level: 1,
- });
- }
-
- return {
- ...rest,
- selector: `xpath=${xpath}`,
- // Provisioning or future use if we want to use direct CDP
- // backendNodeId: elementId,
- };
- }
-
- return {
- ...rest,
- selector: `xpath=${selectorMap[elementId][0]}`,
- // backendNodeId: backendNodeIdMap[elementId],
- };
- }),
- );
-
- this.logger({
- category: "observation",
- message: "found elements",
- level: 1,
- auxiliary: {
- elements: {
- value: JSON.stringify(elementsWithSelectors),
- type: "object",
- },
- },
- });
-
- if (drawOverlay) {
- await drawObserveOverlay(this.stagehandPage.page, elementsWithSelectors);
- }
-
- await this._recordObservation(instruction, elementsWithSelectors);
- return elementsWithSelectors;
- }
-}
diff --git a/lib/handlers/operatorHandler.ts b/lib/handlers/operatorHandler.ts
deleted file mode 100644
index ec5a7d5e5..000000000
--- a/lib/handlers/operatorHandler.ts
+++ /dev/null
@@ -1,242 +0,0 @@
-import { AgentAction, AgentExecuteOptions, AgentResult } from "@/types/agent";
-import { LogLine } from "@/types/log";
-import {
- OperatorResponse,
- operatorResponseSchema,
- OperatorSummary,
- operatorSummarySchema,
-} from "@/types/operator";
-import { LLMParsedResponse } from "../inference";
-import { ChatMessage, LLMClient } from "../llm/LLMClient";
-import { buildOperatorSystemPrompt } from "../prompt";
-import { StagehandPage } from "../StagehandPage";
-import { ObserveResult } from "@/types/stagehand";
-import {
- StagehandError,
- StagehandMissingArgumentError,
-} from "@/types/stagehandErrors";
-
-export class StagehandOperatorHandler {
- private stagehandPage: StagehandPage;
- private logger: (message: LogLine) => void;
- private llmClient: LLMClient;
- private messages: ChatMessage[];
-
- constructor(
- stagehandPage: StagehandPage,
- logger: (message: LogLine) => void,
- llmClient: LLMClient,
- ) {
- this.stagehandPage = stagehandPage;
- this.logger = logger;
- this.llmClient = llmClient;
- }
-
- public async execute(
- instructionOrOptions: string | AgentExecuteOptions,
- ): Promise {
- const options =
- typeof instructionOrOptions === "string"
- ? { instruction: instructionOrOptions }
- : instructionOrOptions;
-
- this.messages = [buildOperatorSystemPrompt(options.instruction)];
- let completed = false;
- let currentStep = 0;
- const maxSteps = options.maxSteps || 10;
- const actions: AgentAction[] = [];
-
- while (!completed && currentStep < maxSteps) {
- const url = this.stagehandPage.page.url();
-
- if (!url || url === "about:blank") {
- this.messages.push({
- role: "user",
- content: [
- {
- type: "text",
- text: "No page is currently loaded. The first step should be a 'goto' action to navigate to a URL.",
- },
- ],
- });
- } else {
- const screenshot = await this.stagehandPage.page.screenshot({
- type: "png",
- fullPage: false,
- });
-
- const base64Image = screenshot.toString("base64");
-
- let messageText = `Here is a screenshot of the current page (URL: ${url}):`;
-
- messageText = `Previous actions were: ${actions
- .map((action) => {
- let result: string = "";
- if (action.type === "act") {
- const args = action.playwrightArguments as ObserveResult;
- result = `Performed a "${args.method}" action ${args.arguments.length > 0 ? `with arguments: ${args.arguments.map((arg) => `"${arg}"`).join(", ")}` : ""} on "${args.description}"`;
- } else if (action.type === "extract") {
- result = `Extracted data: ${action.extractionResult}`;
- }
- return `[${action.type}] ${action.reasoning}. Result: ${result}`;
- })
- .join("\n")}\n\n${messageText}`;
-
- this.messages.push({
- role: "user",
- content: [
- {
- type: "text",
- text: messageText,
- },
- this.llmClient.type === "anthropic"
- ? {
- type: "image",
- source: {
- type: "base64",
- media_type: "image/png",
- data: base64Image,
- },
- text: "the screenshot of the current page",
- }
- : {
- type: "image_url",
- image_url: { url: `data:image/png;base64,${base64Image}` },
- },
- ],
- });
- }
-
- const result = await this.getNextStep(currentStep);
-
- if (result.method === "close") {
- completed = true;
- }
-
- let playwrightArguments: ObserveResult | undefined;
- if (result.method === "act") {
- [playwrightArguments] = await this.stagehandPage.page.observe(
- result.parameters,
- );
- }
- let extractionResult: unknown | undefined;
- if (result.method === "extract") {
- extractionResult = await this.stagehandPage.page.extract(
- result.parameters,
- );
- }
-
- await this.executeAction(result, playwrightArguments, extractionResult);
-
- actions.push({
- type: result.method,
- reasoning: result.reasoning,
- taskCompleted: result.taskComplete,
- parameters: result.parameters,
- playwrightArguments,
- extractionResult,
- });
-
- currentStep++;
- }
-
- return {
- success: true,
- message: await this.getSummary(options.instruction),
- actions,
- completed: actions[actions.length - 1].taskCompleted as boolean,
- };
- }
-
- private async getNextStep(currentStep: number): Promise {
- const { data: response } =
- (await this.llmClient.createChatCompletion({
- options: {
- messages: this.messages,
- response_model: {
- name: "operatorResponseSchema",
- schema: operatorResponseSchema,
- },
- requestId: `operator-step-${currentStep}`,
- },
- logger: this.logger,
- })) as LLMParsedResponse;
-
- return response;
- }
-
- private async getSummary(goal: string): Promise {
- const { data: response } =
- (await this.llmClient.createChatCompletion({
- options: {
- messages: [
- ...this.messages,
- {
- role: "user",
- content: [
- {
- type: "text",
- text: `Now use the steps taken to answer the original instruction of ${goal}.`,
- },
- ],
- },
- ],
- response_model: {
- name: "operatorSummarySchema",
- schema: operatorSummarySchema,
- },
- requestId: "operator-summary",
- },
- logger: this.logger,
- })) as LLMParsedResponse;
-
- return response.answer;
- }
- private async executeAction(
- action: OperatorResponse,
- playwrightArguments?: ObserveResult,
- extractionResult?: unknown,
- ): Promise {
- const { method, parameters } = action;
- const page = this.stagehandPage.page;
-
- if (method === "close") {
- return;
- }
-
- switch (method) {
- case "act":
- if (!playwrightArguments) {
- throw new StagehandMissingArgumentError(
- "No arguments provided to `act()`. " +
- "Please ensure that all required arguments are passed in.",
- );
- }
- await page.act(playwrightArguments);
- break;
- case "extract":
- if (!extractionResult) {
- throw new StagehandError(
- "Error in OperatorHandler: Cannot complete extraction. No extractionResult provided.",
- );
- }
- return extractionResult;
- case "goto":
- await page.goto(parameters, { waitUntil: "load" });
- break;
- case "wait":
- await page.waitForTimeout(parseInt(parameters));
- break;
- case "navback":
- await page.goBack();
- break;
- case "refresh":
- await page.reload();
- break;
- default:
- throw new StagehandError(
- `Error in OperatorHandler: Cannot execute unknown action: ${method}`,
- );
- }
- }
-}
diff --git a/lib/index.ts b/lib/index.ts
deleted file mode 100644
index a12b63a72..000000000
--- a/lib/index.ts
+++ /dev/null
@@ -1,887 +0,0 @@
-import { Browserbase } from "@browserbasehq/sdk";
-import { chromium } from "@playwright/test";
-import dotenv from "dotenv";
-import fs from "fs";
-import os from "os";
-import path from "path";
-import { z } from "zod";
-import { BrowserResult } from "../types/browser";
-import { EnhancedContext } from "../types/context";
-import { LogLine } from "../types/log";
-import { AvailableModel } from "../types/model";
-import { BrowserContext, Page } from "../types/page";
-import {
- ActOptions,
- ActResult,
- ConstructorParams,
- ExtractOptions,
- ExtractResult,
- InitResult,
- LocalBrowserLaunchOptions,
- ObserveOptions,
- ObserveResult,
- AgentConfig,
- StagehandMetrics,
- StagehandFunctionName,
- HistoryEntry,
-} from "../types/stagehand";
-import { StagehandContext } from "./StagehandContext";
-import { StagehandPage } from "./StagehandPage";
-import { StagehandAPI } from "./api";
-import { scriptContent } from "./dom/build/scriptContent";
-import { LLMClient } from "./llm/LLMClient";
-import { LLMProvider } from "./llm/LLMProvider";
-import { isRunningInBun } from "./utils";
-import { ApiResponse, ErrorResponse } from "@/types/api";
-import { AgentExecuteOptions, AgentResult } from "../types/agent";
-import { StagehandAgentHandler } from "./handlers/agentHandler";
-import { StagehandOperatorHandler } from "./handlers/operatorHandler";
-import { StagehandLogger } from "./logger";
-
-import {
- StagehandError,
- StagehandNotInitializedError,
- StagehandEnvironmentError,
- MissingEnvironmentVariableError,
- UnsupportedModelError,
-} from "../types/stagehandErrors";
-
-dotenv.config({ path: ".env" });
-
-const DEFAULT_MODEL_NAME = "gpt-4o";
-
-// Initialize the global logger
-let globalLogger: StagehandLogger;
-
-const defaultLogger = async (logLine: LogLine, disablePino?: boolean) => {
- if (!globalLogger) {
- globalLogger = new StagehandLogger(
- {
- pretty: true,
- usePino: !disablePino,
- },
- undefined,
- );
- }
- globalLogger.log(logLine);
-};
-
-async function getBrowser(
- apiKey: string | undefined,
- projectId: string | undefined,
- env: "LOCAL" | "BROWSERBASE" = "LOCAL",
- headless: boolean = false,
- logger: (message: LogLine) => void,
- browserbaseSessionCreateParams?: Browserbase.Sessions.SessionCreateParams,
- browserbaseSessionID?: string,
- localBrowserLaunchOptions?: LocalBrowserLaunchOptions,
-): Promise {
- if (env === "BROWSERBASE") {
- if (!apiKey) {
- throw new MissingEnvironmentVariableError(
- "BROWSERBASE_API_KEY",
- "Browserbase",
- );
- }
- if (!projectId) {
- throw new MissingEnvironmentVariableError(
- "BROWSERBASE_PROJECT_ID",
- "Browserbase",
- );
- }
-
- let debugUrl: string | undefined = undefined;
- let sessionUrl: string | undefined = undefined;
- let sessionId: string;
- let connectUrl: string;
-
- const browserbase = new Browserbase({
- apiKey,
- });
-
- if (browserbaseSessionID) {
- // Validate the session status
- try {
- const session =
- await browserbase.sessions.retrieve(browserbaseSessionID);
-
- if (session.status !== "RUNNING") {
- throw new StagehandError(
- `Session ${browserbaseSessionID} is not running (status: ${session.status})`,
- );
- }
-
- sessionId = browserbaseSessionID;
- connectUrl = session.connectUrl;
-
- logger({
- category: "init",
- message: "resuming existing browserbase session...",
- level: 1,
- auxiliary: {
- sessionId: {
- value: sessionId,
- type: "string",
- },
- },
- });
- } catch (error) {
- logger({
- category: "init",
- message: "failed to resume session",
- level: 0,
- auxiliary: {
- error: {
- value: error.message,
- type: "string",
- },
- trace: {
- value: error.stack,
- type: "string",
- },
- },
- });
- throw error;
- }
- } else {
- // Create new session (existing code)
- logger({
- category: "init",
- message: "creating new browserbase session...",
- level: 1,
- });
-
- if (!projectId) {
- throw new StagehandError(
- "BROWSERBASE_PROJECT_ID is required for new Browserbase sessions.",
- );
- }
-
- const session = await browserbase.sessions.create({
- projectId,
- ...browserbaseSessionCreateParams,
- userMetadata: {
- ...(browserbaseSessionCreateParams?.userMetadata || {}),
- stagehand: "true",
- },
- });
-
- sessionId = session.id;
- connectUrl = session.connectUrl;
- logger({
- category: "init",
- message: "created new browserbase session",
- level: 1,
- auxiliary: {
- sessionId: {
- value: sessionId,
- type: "string",
- },
- },
- });
- }
- if (!connectUrl.includes("connect.connect")) {
- logger({
- category: "init",
- message: "connecting to browserbase session",
- level: 1,
- auxiliary: {
- connectUrl: {
- value: connectUrl,
- type: "string",
- },
- },
- });
- }
- 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: browserbaseSessionID
- ? "browserbase session resumed"
- : "browserbase session started",
- auxiliary: {
- sessionUrl: {
- value: sessionUrl,
- type: "string",
- },
- debugUrl: {
- value: debugUrl,
- type: "string",
- },
- sessionId: {
- value: sessionId,
- type: "string",
- },
- },
- });
-
- const context = browser.contexts()[0];
-
- return { browser, context, debugUrl, sessionUrl, sessionId, env };
- } else {
- if (localBrowserLaunchOptions?.cdpUrl) {
- if (!localBrowserLaunchOptions.cdpUrl.includes("connect.connect")) {
- logger({
- category: "init",
- message: "connecting to local browser via CDP URL",
- level: 1,
- auxiliary: {
- cdpUrl: {
- value: localBrowserLaunchOptions.cdpUrl,
- type: "string",
- },
- },
- });
- }
-
- const browser = await chromium.connectOverCDP(
- localBrowserLaunchOptions.cdpUrl,
- );
- const context = browser.contexts()[0];
- return { browser, context, env: "LOCAL" };
- }
-
- let userDataDir = localBrowserLaunchOptions?.userDataDir;
- if (!userDataDir) {
- const tmpDirPath = path.join(os.tmpdir(), "stagehand");
- if (!fs.existsSync(tmpDirPath)) {
- fs.mkdirSync(tmpDirPath, { recursive: true });
- }
-
- const tmpDir = fs.mkdtempSync(path.join(tmpDirPath, "ctx_"));
- fs.mkdirSync(path.join(tmpDir, "userdir/Default"), { recursive: true });
-
- const defaultPreferences = {
- plugins: {
- always_open_pdf_externally: true,
- },
- };
-
- fs.writeFileSync(
- path.join(tmpDir, "userdir/Default/Preferences"),
- JSON.stringify(defaultPreferences),
- );
- userDataDir = path.join(tmpDir, "userdir");
- }
-
- let downloadsPath = localBrowserLaunchOptions?.downloadsPath;
- if (!downloadsPath) {
- downloadsPath = path.join(process.cwd(), "downloads");
- fs.mkdirSync(downloadsPath, { recursive: true });
- }
-
- const context = await chromium.launchPersistentContext(userDataDir, {
- acceptDownloads: localBrowserLaunchOptions?.acceptDownloads ?? true,
- headless: localBrowserLaunchOptions?.headless ?? headless,
- viewport: {
- width: localBrowserLaunchOptions?.viewport?.width ?? 1024,
- height: localBrowserLaunchOptions?.viewport?.height ?? 768,
- },
- locale: localBrowserLaunchOptions?.locale ?? "en-US",
- timezoneId: localBrowserLaunchOptions?.timezoneId ?? "America/New_York",
- deviceScaleFactor: localBrowserLaunchOptions?.deviceScaleFactor ?? 1,
- args: localBrowserLaunchOptions?.args ?? [
- "--enable-webgl",
- "--use-gl=swiftshader",
- "--enable-accelerated-2d-canvas",
- "--disable-blink-features=AutomationControlled",
- "--disable-web-security",
- ],
- bypassCSP: localBrowserLaunchOptions?.bypassCSP ?? true,
- proxy: localBrowserLaunchOptions?.proxy,
- geolocation: localBrowserLaunchOptions?.geolocation,
- hasTouch: localBrowserLaunchOptions?.hasTouch ?? true,
- ignoreHTTPSErrors: localBrowserLaunchOptions?.ignoreHTTPSErrors ?? true,
- permissions: localBrowserLaunchOptions?.permissions,
- recordHar: localBrowserLaunchOptions?.recordHar,
- recordVideo: localBrowserLaunchOptions?.recordVideo,
- tracesDir: localBrowserLaunchOptions?.tracesDir,
- extraHTTPHeaders: localBrowserLaunchOptions?.extraHTTPHeaders,
- chromiumSandbox: localBrowserLaunchOptions?.chromiumSandbox ?? false,
- devtools: localBrowserLaunchOptions?.devtools ?? false,
- env: localBrowserLaunchOptions?.env,
- executablePath: localBrowserLaunchOptions?.executablePath,
- handleSIGHUP: localBrowserLaunchOptions?.handleSIGHUP ?? true,
- handleSIGINT: localBrowserLaunchOptions?.handleSIGINT ?? true,
- handleSIGTERM: localBrowserLaunchOptions?.handleSIGTERM ?? true,
- ignoreDefaultArgs: localBrowserLaunchOptions?.ignoreDefaultArgs,
- });
-
- if (localBrowserLaunchOptions?.cookies) {
- context.addCookies(localBrowserLaunchOptions.cookies);
- }
-
- logger({
- category: "init",
- message: "local browser started successfully.",
- });
-
- await applyStealthScripts(context);
-
- return { context, contextPath: userDataDir, env: "LOCAL" };
- }
-}
-
-async function applyStealthScripts(context: BrowserContext) {
- await context.addInitScript(() => {
- // Override the navigator.webdriver property
- Object.defineProperty(navigator, "webdriver", {
- get: () => undefined,
- });
-
- // Mock languages and plugins to mimic a real browser
- Object.defineProperty(navigator, "languages", {
- get: () => ["en-US", "en"],
- });
-
- Object.defineProperty(navigator, "plugins", {
- get: () => [1, 2, 3, 4, 5],
- });
-
- // Remove Playwright-specific properties
- delete window.__playwright;
- delete window.__pw_manual;
- delete window.__PW_inspect;
-
- // Redefine the headless property
- Object.defineProperty(navigator, "headless", {
- get: () => false,
- });
-
- // Override the permissions API
- const originalQuery = window.navigator.permissions.query;
- window.navigator.permissions.query = (parameters) =>
- parameters.name === "notifications"
- ? Promise.resolve({
- state: Notification.permission,
- } as PermissionStatus)
- : originalQuery(parameters);
- });
-}
-
-export class Stagehand {
- private stagehandPage!: StagehandPage;
- private stagehandContext!: StagehandContext;
- public browserbaseSessionID?: string;
- public readonly domSettleTimeoutMs: number;
- public readonly debugDom: boolean;
- public readonly headless: boolean;
- public verbose: 0 | 1 | 2;
- public llmProvider: LLMProvider;
- public enableCaching: boolean;
- private apiKey: string | undefined;
- private projectId: string | undefined;
- private externalLogger?: (logLine: LogLine) => void;
- private browserbaseSessionCreateParams?: Browserbase.Sessions.SessionCreateParams;
- public variables: { [key: string]: unknown };
- private contextPath?: string;
- public llmClient: LLMClient;
- public readonly userProvidedInstructions?: string;
- private usingAPI: boolean;
- private modelName: AvailableModel;
- public apiClient: StagehandAPI | undefined;
- public readonly waitForCaptchaSolves: boolean;
- private localBrowserLaunchOptions?: LocalBrowserLaunchOptions;
- public readonly selfHeal: boolean;
- private cleanupCalled = false;
- public readonly actTimeoutMs: number;
- public readonly logInferenceToFile?: boolean;
- private stagehandLogger: StagehandLogger;
- private disablePino: boolean;
- private _env: "LOCAL" | "BROWSERBASE";
-
- protected setActivePage(page: StagehandPage): void {
- this.stagehandPage = page;
- }
-
- public get page(): Page {
- if (!this.stagehandContext) {
- throw new StagehandNotInitializedError("page");
- }
- return this.stagehandPage.page;
- }
-
- public stagehandMetrics: StagehandMetrics = {
- actPromptTokens: 0,
- actCompletionTokens: 0,
- actInferenceTimeMs: 0,
- extractPromptTokens: 0,
- extractCompletionTokens: 0,
- extractInferenceTimeMs: 0,
- observePromptTokens: 0,
- observeCompletionTokens: 0,
- observeInferenceTimeMs: 0,
- totalPromptTokens: 0,
- totalCompletionTokens: 0,
- totalInferenceTimeMs: 0,
- };
-
- public get metrics(): StagehandMetrics {
- return this.stagehandMetrics;
- }
-
- public updateMetrics(
- functionName: StagehandFunctionName,
- promptTokens: number,
- completionTokens: number,
- inferenceTimeMs: number,
- ): void {
- switch (functionName) {
- case StagehandFunctionName.ACT:
- this.stagehandMetrics.actPromptTokens += promptTokens;
- this.stagehandMetrics.actCompletionTokens += completionTokens;
- this.stagehandMetrics.actInferenceTimeMs += inferenceTimeMs;
- break;
-
- case StagehandFunctionName.EXTRACT:
- this.stagehandMetrics.extractPromptTokens += promptTokens;
- this.stagehandMetrics.extractCompletionTokens += completionTokens;
- this.stagehandMetrics.extractInferenceTimeMs += inferenceTimeMs;
- break;
-
- case StagehandFunctionName.OBSERVE:
- this.stagehandMetrics.observePromptTokens += promptTokens;
- this.stagehandMetrics.observeCompletionTokens += completionTokens;
- this.stagehandMetrics.observeInferenceTimeMs += inferenceTimeMs;
- break;
- }
- this.updateTotalMetrics(promptTokens, completionTokens, inferenceTimeMs);
- }
-
- private updateTotalMetrics(
- promptTokens: number,
- completionTokens: number,
- inferenceTimeMs: number,
- ): void {
- this.stagehandMetrics.totalPromptTokens += promptTokens;
- this.stagehandMetrics.totalCompletionTokens += completionTokens;
- this.stagehandMetrics.totalInferenceTimeMs += inferenceTimeMs;
- }
-
- constructor(
- {
- env,
- apiKey,
- projectId,
- verbose,
- llmProvider,
- llmClient,
- logger,
- browserbaseSessionCreateParams,
- domSettleTimeoutMs,
- enableCaching,
- browserbaseSessionID,
- modelName,
- modelClientOptions,
- systemPrompt,
- useAPI,
- localBrowserLaunchOptions,
- waitForCaptchaSolves = false,
- logInferenceToFile = false,
- selfHeal = false,
- disablePino,
- }: ConstructorParams = {
- env: "BROWSERBASE",
- },
- ) {
- this.externalLogger =
- logger || ((logLine: LogLine) => defaultLogger(logLine, disablePino));
-
- // Initialize the Stagehand logger
- this.stagehandLogger = new StagehandLogger(
- {
- pretty: true,
- // use pino if pino is enabled, and there is no custom logger
- usePino: !logger && !disablePino,
- },
- this.externalLogger,
- );
-
- this.enableCaching =
- enableCaching ??
- (process.env.ENABLE_CACHING && process.env.ENABLE_CACHING === "true");
-
- this.llmProvider =
- llmProvider || new LLMProvider(this.logger, this.enableCaching);
-
- this.apiKey = apiKey ?? process.env.BROWSERBASE_API_KEY;
- this.projectId = projectId ?? process.env.BROWSERBASE_PROJECT_ID;
-
- // Store the environment value
- this._env = env ?? "BROWSERBASE";
-
- if (this._env === "BROWSERBASE") {
- if (!this.apiKey) {
- throw new MissingEnvironmentVariableError(
- "BROWSERBASE_API_KEY",
- "Browserbase",
- );
- } else if (!this.projectId) {
- throw new MissingEnvironmentVariableError(
- "BROWSERBASE_PROJECT_ID",
- "Browserbase",
- );
- }
- }
-
- this.verbose = verbose ?? 0;
- // Update logger verbosity level
- this.stagehandLogger.setVerbosity(this.verbose);
-
- if (llmClient) {
- this.llmClient = llmClient;
- } else {
- try {
- // try to set a default LLM client
- this.llmClient = this.llmProvider.getClient(
- modelName ?? DEFAULT_MODEL_NAME,
- modelClientOptions,
- );
- } catch {
- this.llmClient = undefined;
- }
- }
-
- this.domSettleTimeoutMs = domSettleTimeoutMs ?? 30_000;
- this.headless = localBrowserLaunchOptions?.headless ?? false;
- this.browserbaseSessionCreateParams = browserbaseSessionCreateParams;
- this.browserbaseSessionID = browserbaseSessionID;
- this.userProvidedInstructions = systemPrompt;
- this.usingAPI = useAPI ?? false;
- this.modelName = modelName ?? DEFAULT_MODEL_NAME;
- if (this.usingAPI && env === "LOCAL") {
- throw new StagehandEnvironmentError("LOCAL", "BROWSERBASE", "API mode");
- } else if (this.usingAPI && !process.env.STAGEHAND_API_URL) {
- throw new MissingEnvironmentVariableError(
- "STAGEHAND_API_URL",
- "API mode",
- );
- } else if (
- this.usingAPI &&
- this.llmClient &&
- this.llmClient.type !== "openai" &&
- this.llmClient.type !== "anthropic" &&
- this.llmClient.type !== "google"
- ) {
- throw new UnsupportedModelError(
- ["openai", "anthropic", "google"],
- "API mode",
- );
- }
- this.waitForCaptchaSolves = waitForCaptchaSolves;
- this.localBrowserLaunchOptions = localBrowserLaunchOptions;
-
- if (this.usingAPI) {
- this.registerSignalHandlers();
- }
- this.logInferenceToFile = logInferenceToFile;
- this.selfHeal = selfHeal;
- this.disablePino = disablePino;
- }
-
- private registerSignalHandlers() {
- const cleanup = async (signal: string) => {
- if (this.cleanupCalled) return;
- this.cleanupCalled = true;
-
- this.stagehandLogger.info(
- `[${signal}] received. Ending Browserbase session...`,
- );
- try {
- await this.close();
- } catch (err) {
- this.stagehandLogger.error("Error ending Browserbase session:", {
- error: String(err),
- });
- } finally {
- // Exit explicitly once cleanup is done
- process.exit(0);
- }
- };
-
- process.once("SIGINT", () => void cleanup("SIGINT"));
- process.once("SIGTERM", () => void cleanup("SIGTERM"));
- }
-
- public get logger(): (logLine: LogLine) => void {
- return (logLine: LogLine) => {
- this.log(logLine);
- };
- }
-
- public get env(): "LOCAL" | "BROWSERBASE" {
- if (this._env === "BROWSERBASE") {
- if (!this.apiKey) {
- throw new MissingEnvironmentVariableError(
- "BROWSERBASE_API_KEY",
- "Browserbase",
- );
- } else if (!this.projectId) {
- throw new MissingEnvironmentVariableError(
- "BROWSERBASE_PROJECT_ID",
- "Browserbase",
- );
- }
- return "BROWSERBASE";
- } else {
- return "LOCAL";
- }
- }
-
- public get context(): EnhancedContext {
- if (!this.stagehandContext) {
- throw new StagehandNotInitializedError("context");
- }
- return this.stagehandContext.context;
- }
-
- async init(): Promise {
- if (isRunningInBun()) {
- throw new StagehandError(
- "Playwright does not currently support the Bun runtime environment. " +
- "Please use Node.js instead. For more information, see: " +
- "https://github.com/microsoft/playwright/issues/27139",
- );
- }
-
- if (this.usingAPI) {
- this.apiClient = new StagehandAPI({
- apiKey: this.apiKey,
- projectId: this.projectId,
- logger: this.logger,
- });
- const modelApiKey =
- LLMProvider.getModelProvider(this.modelName) === "openai"
- ? process.env.OPENAI_API_KEY || this.llmClient.clientOptions.apiKey
- : LLMProvider.getModelProvider(this.modelName) === "anthropic"
- ? process.env.ANTHROPIC_API_KEY ||
- this.llmClient.clientOptions.apiKey
- : LLMProvider.getModelProvider(this.modelName) === "google"
- ? process.env.GOOGLE_API_KEY ||
- this.llmClient.clientOptions.apiKey
- : undefined;
-
- const { sessionId } = await this.apiClient.init({
- modelName: this.modelName,
- modelApiKey: modelApiKey,
- domSettleTimeoutMs: this.domSettleTimeoutMs,
- verbose: this.verbose,
- debugDom: this.debugDom,
- systemPrompt: this.userProvidedInstructions,
- selfHeal: this.selfHeal,
- waitForCaptchaSolves: this.waitForCaptchaSolves,
- actionTimeoutMs: this.actTimeoutMs,
- browserbaseSessionCreateParams: this.browserbaseSessionCreateParams,
- browserbaseSessionID: this.browserbaseSessionID,
- });
- this.browserbaseSessionID = sessionId;
- }
-
- const { context, debugUrl, sessionUrl, contextPath, sessionId } =
- await getBrowser(
- this.apiKey,
- this.projectId,
- this.env,
- this.headless,
- this.logger,
- this.browserbaseSessionCreateParams,
- this.browserbaseSessionID,
- this.localBrowserLaunchOptions,
- ).catch((e) => {
- this.stagehandLogger.error("Error in init:", { error: String(e) });
- const br: BrowserResult = {
- context: undefined,
- debugUrl: undefined,
- sessionUrl: undefined,
- sessionId: undefined,
- env: this.env,
- };
- return br;
- });
- this.contextPath = contextPath;
-
- this.stagehandContext = await StagehandContext.init(context, this);
-
- const defaultPage = (await this.stagehandContext.getStagehandPages())[0];
- this.stagehandPage = defaultPage;
-
- if (this.headless) {
- await this.page.setViewportSize({ width: 1280, height: 720 });
- }
-
- await this.context.addInitScript({
- content: scriptContent,
- });
-
- this.browserbaseSessionID = sessionId;
-
- return { debugUrl, sessionUrl, sessionId };
- }
-
- log(logObj: LogLine): void {
- logObj.level = logObj.level ?? 1;
-
- // Use our Pino-based logger
- this.stagehandLogger.log(logObj);
- }
-
- /** @deprecated Use stagehand.page.act() instead. This will be removed in the next major release. */
- async act(options: ActOptions): Promise {
- return await this.stagehandPage.act(options);
- }
-
- /** @deprecated Use stagehand.page.extract() instead. This will be removed in the next major release. */
- async extract(
- options: ExtractOptions,
- ): Promise> {
- return await this.stagehandPage.extract(options);
- }
-
- /** @deprecated Use stagehand.page.observe() instead. This will be removed in the next major release. */
- async observe(options?: ObserveOptions): Promise {
- return await this.stagehandPage.observe(options);
- }
-
- async close(): Promise {
- if (this.apiClient) {
- const response = await this.apiClient.end();
- const body: ApiResponse = await response.json();
- if (!body.success) {
- if (response.status == 409) {
- this.log({
- category: "close",
- message:
- "Warning: attempted to end a session that is not currently active",
- level: 0,
- });
- } else {
- throw new StagehandError((body as ErrorResponse).message);
- }
- }
- return;
- } else {
- await this.context.close();
- }
-
- if (this.contextPath) {
- try {
- fs.rmSync(this.contextPath, { recursive: true, force: true });
- } catch (e) {
- console.error("Error deleting context directory:", e);
- }
- }
- }
-
- /**
- * Create an agent instance that can be executed with different instructions
- * @returns An agent instance with execute() method
- */
- agent(options?: AgentConfig): {
- execute: (
- instructionOrOptions: string | AgentExecuteOptions,
- ) => Promise;
- } {
- if (!options || !options.provider) {
- // use open operator agent
- return {
- execute: async (instructionOrOptions: string | AgentExecuteOptions) => {
- return new StagehandOperatorHandler(
- this.stagehandPage,
- this.logger,
- this.llmClient,
- ).execute(instructionOrOptions);
- },
- };
- }
-
- const agentHandler = new StagehandAgentHandler(
- this.stagehandPage,
- this.logger,
- {
- modelName: options.model,
- clientOptions: options.options,
- userProvidedInstructions:
- options.instructions ??
- `You are a helpful assistant that can use a web browser.
- You are currently on the following page: ${this.stagehandPage.page.url()}.
- Do not ask follow up questions, the user will trust your judgement.`,
- agentType: options.provider,
- },
- );
-
- this.log({
- category: "agent",
- message: "Creating agent instance",
- level: 1,
- });
-
- return {
- execute: async (instructionOrOptions: string | AgentExecuteOptions) => {
- const executeOptions: AgentExecuteOptions =
- typeof instructionOrOptions === "string"
- ? { instruction: instructionOrOptions }
- : instructionOrOptions;
-
- if (!executeOptions.instruction) {
- throw new StagehandError(
- "Instruction is required for agent execution",
- );
- }
-
- if (this.usingAPI) {
- if (!this.apiClient) {
- throw new StagehandNotInitializedError("API client");
- }
-
- if (!options.options) {
- options.options = {};
- }
-
- if (options.provider === "anthropic") {
- options.options.apiKey = process.env.ANTHROPIC_API_KEY;
- } else if (options.provider === "openai") {
- options.options.apiKey = process.env.OPENAI_API_KEY;
- } else if (options.provider === "google") {
- options.options.apiKey = process.env.GOOGLE_API_KEY;
- }
-
- if (!options.options.apiKey) {
- throw new StagehandError(
- `API key not found for \`${options.provider}\` provider. Please set the ${options.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"} environment variable or pass an apiKey in the options object.`,
- );
- }
-
- return await this.apiClient.agentExecute(options, executeOptions);
- }
-
- return await agentHandler.execute(executeOptions);
- },
- };
- }
-
- public get history(): ReadonlyArray {
- if (!this.stagehandPage) {
- throw new StagehandNotInitializedError("history()");
- }
-
- return this.stagehandPage.history;
- }
-}
-
-export * from "../types/browser";
-export * from "../types/log";
-export * from "../types/model";
-export * from "../types/page";
-export * from "../types/playwright";
-export * from "../types/stagehand";
-export * from "../types/operator";
-export * from "../types/agent";
-export * from "./llm/LLMClient";
-export * from "../types/stagehandErrors";
-export * from "../types/stagehandApiErrors";
diff --git a/lib/llm/LLMProvider.ts b/lib/llm/LLMProvider.ts
deleted file mode 100644
index f4fc51e64..000000000
--- a/lib/llm/LLMProvider.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import { LogLine } from "../../types/log";
-import {
- AvailableModel,
- ClientOptions,
- ModelProvider,
-} from "../../types/model";
-import { LLMCache } from "../cache/LLMCache";
-import { AnthropicClient } from "./AnthropicClient";
-import { CerebrasClient } from "./CerebrasClient";
-import { GoogleClient } from "./GoogleClient";
-import { GroqClient } from "./GroqClient";
-import { LLMClient } from "./LLMClient";
-import { OpenAIClient } from "./OpenAIClient";
-import {
- UnsupportedModelError,
- UnsupportedModelProviderError,
-} from "@/types/stagehandErrors";
-
-const modelToProviderMap: { [key in AvailableModel]: ModelProvider } = {
- "gpt-4o": "openai",
- "gpt-4o-mini": "openai",
- "gpt-4o-2024-08-06": "openai",
- "gpt-4.5-preview": "openai",
- "o1-mini": "openai",
- "o1-preview": "openai",
- "o3-mini": "openai",
- "claude-3-5-sonnet-latest": "anthropic",
- "claude-3-5-sonnet-20240620": "anthropic",
- "claude-3-5-sonnet-20241022": "anthropic",
- "claude-3-7-sonnet-20250219": "anthropic",
- "claude-3-7-sonnet-latest": "anthropic",
- "cerebras-llama-3.3-70b": "cerebras",
- "cerebras-llama-3.1-8b": "cerebras",
- "groq-llama-3.3-70b-versatile": "groq",
- "groq-llama-3.3-70b-specdec": "groq",
- "gemini-1.5-flash": "google",
- "gemini-1.5-pro": "google",
- "gemini-1.5-flash-8b": "google",
- "gemini-2.0-flash-lite": "google",
- "gemini-2.0-flash": "google",
- "gemini-2.5-flash-preview-04-17": "google",
- "gemini-2.5-pro-preview-03-25": "google",
-};
-
-export class LLMProvider {
- private logger: (message: LogLine) => void;
- private enableCaching: boolean;
- private cache: LLMCache | undefined;
-
- constructor(logger: (message: LogLine) => void, enableCaching: boolean) {
- this.logger = logger;
- this.enableCaching = enableCaching;
- this.cache = enableCaching ? new LLMCache(logger) : undefined;
- }
-
- cleanRequestCache(requestId: string): void {
- if (!this.enableCaching) {
- return;
- }
-
- this.logger({
- category: "llm_cache",
- message: "cleaning up cache",
- level: 1,
- auxiliary: {
- requestId: {
- value: requestId,
- type: "string",
- },
- },
- });
- this.cache.deleteCacheForRequestId(requestId);
- }
-
- getClient(
- modelName: AvailableModel,
- clientOptions?: ClientOptions,
- ): LLMClient {
- const provider = modelToProviderMap[modelName];
- if (!provider) {
- throw new UnsupportedModelError(Object.keys(modelToProviderMap));
- }
-
- switch (provider) {
- case "openai":
- return new OpenAIClient({
- logger: this.logger,
- enableCaching: this.enableCaching,
- cache: this.cache,
- modelName,
- clientOptions,
- });
- case "anthropic":
- return new AnthropicClient({
- logger: this.logger,
- enableCaching: this.enableCaching,
- cache: this.cache,
- modelName,
- clientOptions,
- });
- case "cerebras":
- return new CerebrasClient({
- logger: this.logger,
- enableCaching: this.enableCaching,
- cache: this.cache,
- modelName,
- clientOptions,
- });
- case "groq":
- return new GroqClient({
- logger: this.logger,
- enableCaching: this.enableCaching,
- cache: this.cache,
- modelName,
- clientOptions,
- });
- case "google":
- return new GoogleClient({
- logger: this.logger,
- enableCaching: this.enableCaching,
- cache: this.cache,
- modelName,
- clientOptions,
- });
- default:
- throw new UnsupportedModelProviderError([
- ...new Set(Object.values(modelToProviderMap)),
- ]);
- }
- }
-
- static getModelProvider(modelName: AvailableModel): ModelProvider {
- const provider = modelToProviderMap[modelName];
-
- return provider;
- }
-}
diff --git a/lib/prompt.ts b/lib/prompt.ts
deleted file mode 100644
index 1c414d8aa..000000000
--- a/lib/prompt.ts
+++ /dev/null
@@ -1,235 +0,0 @@
-import { ChatMessage } from "./llm/LLMClient";
-
-export function buildUserInstructionsString(
- userProvidedInstructions?: string,
-): string {
- if (!userProvidedInstructions) {
- return "";
- }
-
- return `\n\n# Custom Instructions Provided by the User
-
-Please keep the user's instructions in mind when performing actions. If the user's instructions are not relevant to the current task, ignore them.
-
-User Instructions:
-${userProvidedInstructions}`;
-}
-
-// extract
-export function buildExtractSystemPrompt(
- isUsingPrintExtractedDataTool: boolean = false,
- useTextExtract: boolean = false,
- userProvidedInstructions?: string,
-): ChatMessage {
- const baseContent = `You are extracting content on behalf of a user.
- If a user asks you to extract a 'list' of information, or 'all' information,
- YOU MUST EXTRACT ALL OF THE INFORMATION THAT THE USER REQUESTS.
-
- You will be given:
-1. An instruction
-2. `;
-
- const contentDetail = useTextExtract
- ? `A text representation of a webpage to extract information from.`
- : `A list of DOM elements to extract from.`;
-
- const instructions = `
-Print the exact text from the ${
- useTextExtract ? "text-rendered webpage" : "DOM elements"
- } with all symbols, characters, and endlines as is.
-Print null or an empty string if no new information is found.
- `.trim();
-
- const toolInstructions = isUsingPrintExtractedDataTool
- ? `
-ONLY print the content using the print_extracted_data tool provided.
-ONLY print the content using the print_extracted_data tool provided.
- `.trim()
- : "";
-
- const additionalInstructions = useTextExtract
- ? `Once you are given the text-rendered webpage,
- you must thoroughly and meticulously analyze it. Be very careful to ensure that you
- do not miss any important information.`
- : "If a user is attempting to extract links or URLs, you MUST respond with ONLY the IDs of the link elements. \n" +
- "Do not attempt to extract links directly from the text unless absolutely necessary. ";
-
- const userInstructions = buildUserInstructionsString(
- userProvidedInstructions,
- );
-
- const content =
- `${baseContent}${contentDetail}\n\n${instructions}\n${toolInstructions}${
- additionalInstructions ? `\n\n${additionalInstructions}` : ""
- }${userInstructions ? `\n\n${userInstructions}` : ""}`.replace(/\s+/g, " ");
-
- return {
- role: "system",
- content,
- };
-}
-
-export function buildExtractUserPrompt(
- instruction: string,
- domElements: string,
- isUsingPrintExtractedDataTool: boolean = false,
-): ChatMessage {
- let content = `Instruction: ${instruction}
-DOM: ${domElements}`;
-
- if (isUsingPrintExtractedDataTool) {
- content += `
-ONLY print the content using the print_extracted_data tool provided.
-ONLY print the content using the print_extracted_data tool provided.`;
- }
-
- return {
- role: "user",
- content,
- };
-}
-
-const refineSystemPrompt = `You are tasked with refining and filtering information for the final output based on newly extracted and previously extracted content. Your responsibilities are:
-1. Remove exact duplicates for elements in arrays and objects.
-2. For text fields, append or update relevant text if the new content is an extension, replacement, or continuation.
-3. For non-text fields (e.g., numbers, booleans), update with new values if they differ.
-4. Add any completely new fields or objects ONLY IF they correspond to the provided schema.
-
-Return the updated content that includes both the previous content and the new, non-duplicate, or extended information.`;
-
-export function buildRefineSystemPrompt(): ChatMessage {
- return {
- role: "system",
- content: refineSystemPrompt,
- };
-}
-
-export function buildRefineUserPrompt(
- instruction: string,
- previouslyExtractedContent: object,
- newlyExtractedContent: object,
-): ChatMessage {
- return {
- role: "user",
- content: `Instruction: ${instruction}
-Previously extracted content: ${JSON.stringify(previouslyExtractedContent, null, 2)}
-Newly extracted content: ${JSON.stringify(newlyExtractedContent, null, 2)}
-Refined content:`,
- };
-}
-
-const metadataSystemPrompt = `You are an AI assistant tasked with evaluating the progress and completion status of an extraction task.
-Analyze the extraction response and determine if the task is completed or if more information is needed.
-Strictly abide by the following criteria:
-1. Once the instruction has been satisfied by the current extraction response, ALWAYS set completion status to true and stop processing, regardless of remaining chunks.
-2. Only set completion status to false if BOTH of these conditions are true:
- - The instruction has not been satisfied yet
- - There are still chunks left to process (chunksTotal > chunksSeen)`;
-
-export function buildMetadataSystemPrompt(): ChatMessage {
- return {
- role: "system",
- content: metadataSystemPrompt,
- };
-}
-
-export function buildMetadataPrompt(
- instruction: string,
- extractionResponse: object,
- chunksSeen: number,
- chunksTotal: number,
-): ChatMessage {
- return {
- role: "user",
- content: `Instruction: ${instruction}
-Extracted content: ${JSON.stringify(extractionResponse, null, 2)}
-chunksSeen: ${chunksSeen}
-chunksTotal: ${chunksTotal}`,
- };
-}
-
-// observe
-export function buildObserveSystemPrompt(
- userProvidedInstructions?: string,
- isUsingAccessibilityTree = false,
-): ChatMessage {
- const observeSystemPrompt = `
-You are helping the user automate the browser by finding elements based on what the user wants to observe in the page.
-
-You will be given:
-1. a instruction of elements to observe
-2. ${
- isUsingAccessibilityTree
- ? "a hierarchical accessibility tree showing the semantic structure of the page. The tree is a hybrid of the DOM and the accessibility tree."
- : "a numbered list of possible elements"
- }
-
-Return an array of elements that match the instruction if they exist, otherwise return an empty array.`;
- const content = observeSystemPrompt.replace(/\s+/g, " ");
-
- return {
- role: "system",
- content: [content, buildUserInstructionsString(userProvidedInstructions)]
- .filter(Boolean)
- .join("\n\n"),
- };
-}
-
-export function buildObserveUserMessage(
- instruction: string,
- domElements: string,
- isUsingAccessibilityTree = false,
-): ChatMessage {
- return {
- role: "user",
- content: `instruction: ${instruction}
-${isUsingAccessibilityTree ? "Accessibility Tree" : "DOM"}: ${domElements}`,
- };
-}
-
-/**
- * Builds the instruction for the observeAct method to find the most relevant element for an action
- */
-export function buildActObservePrompt(
- action: string,
- supportedActions: string[],
- variables?: Record,
-): string {
- // Base instruction
- let instruction = `Find the most relevant element to perform an action on given the following action: ${action}.
- Provide an action for this element such as ${supportedActions.join(", ")}, or any other playwright locator method. Remember that to users, buttons and links look the same in most cases.
- If the action is completely unrelated to a potential action to be taken on the page, return an empty array.
- ONLY return one action. If multiple actions are relevant, return the most relevant one.
- If the user is asking to scroll to a position on the page, e.g., 'halfway' or 0.75, etc, you must return the argument formatted as the correct percentage, e.g., '50%' or '75%', etc.
- If the user is asking to scroll to the next chunk/previous chunk, choose the nextChunk/prevChunk method. No arguments are required here.
- If the action implies a key press, e.g., 'press enter', 'press a', 'press space', etc., always choose the press method with the appropriate key as argument — e.g. 'a', 'Enter', 'Space'. Do not choose a click action on an on-screen keyboard. Capitalize the first character like 'Enter', 'Tab', 'Escape' only for special keys.`;
-
- // Add variable names (not values) to the instruction if any
- if (variables && Object.keys(variables).length > 0) {
- const variablesPrompt = `The following variables are available to use in the action: ${Object.keys(variables).join(", ")}. Fill the argument variables with the variable name.`;
- instruction += ` ${variablesPrompt}`;
- }
-
- return instruction;
-}
-
-export function buildOperatorSystemPrompt(goal: string): ChatMessage {
- return {
- role: "system",
- content: `You are a general-purpose agent whose job is to accomplish the user's goal across multiple model calls by running actions on the page.
-
-You will be given a goal and a list of steps that have been taken so far. Your job is to determine if either the user's goal has been completed or if there are still steps that need to be taken.
-
-# Your current goal
-${goal}
-
-# Important guidelines
-1. Break down complex actions into individual atomic steps
-2. For \`act\` commands, use only one action at a time, such as:
- - Single click on a specific element
- - Type into a single input field
- - Select a single option
-3. Avoid combining multiple actions in one instruction
-4. If multiple actions are needed, they should be separate steps`,
- };
-}
diff --git a/lib/utils.ts b/lib/utils.ts
deleted file mode 100644
index 0887bcb11..000000000
--- a/lib/utils.ts
+++ /dev/null
@@ -1,444 +0,0 @@
-import crypto from "crypto";
-import { z } from "zod";
-import { ObserveResult, Page } from ".";
-import { LogLine } from "../types/log";
-import { TextAnnotation } from "../types/textannotation";
-
-// This is a heuristic for the width of a character in pixels. It seems to work
-// better than attempting to calculate character widths dynamically, which sometimes
-// results in collisions when placing characters on the "canvas".
-const HEURISTIC_CHAR_WIDTH = 5;
-
-export function generateId(operation: string) {
- return crypto.createHash("sha256").update(operation).digest("hex");
-}
-
-/**
- * `formatText` converts a list of text annotations into a formatted text representation.
- * Each annotation represents a piece of text at a certain position on a webpage.
- * The formatting attempts to reconstruct a textual "screenshot" of the page by:
- * - Grouping annotations into lines based on their vertical positions.
- * - Adjusting spacing to reflect line gaps.
- * - Attempting to preserve relative positions and formatting.
- *
- * The output is a text block, optionally surrounded by lines of dashes, that aims
- * to closely mirror the visual layout of the text on the page.
- *
- * @param textAnnotations - An array of TextAnnotations describing text and their positions.
- * @param pageWidth - The width of the page in pixels, used to normalize positions.
- * @returns A string representing the text layout of the page.
- */
-export function formatText(
- textAnnotations: TextAnnotation[],
- pageWidth: number,
-): string {
- // **1: Sort annotations by vertical position (y-coordinate).**
- // The topmost annotations appear first, the bottommost last.
- const sortedAnnotations = [...textAnnotations].sort(
- (a, b) => a.bottom_left.y - b.bottom_left.y,
- );
-
- // **2: Group annotations by line based on their y-coordinate.**
- // We use an epsilon so that very close y-values are treated as the same line.
- const epsilon = 1;
- const lineMap: Map = new Map();
-
- for (const annotation of sortedAnnotations) {
- let foundLineY: number | undefined;
- // **3: Check if this annotation belongs to any existing line group.**
- for (const key of lineMap.keys()) {
- if (Math.abs(key - annotation.bottom_left.y) < epsilon) {
- foundLineY = key;
- break;
- }
- }
-
- // If found, push into that line; otherwise, create a new line entry.
- if (foundLineY !== undefined) {
- lineMap.get(foundLineY)!.push(annotation);
- } else {
- lineMap.set(annotation.bottom_left.y, [annotation]);
- }
- }
-
- // **4: Get all unique y-coordinates for lines and sort them top-to-bottom.**
- const lineYs = Array.from(lineMap.keys()).sort((a, b) => a - b);
-
- // **5: Build an array of "final lines" (TextAnnotations[]) by grouping words for each line.**
- const finalLines: TextAnnotation[][] = [];
-
- for (const lineY of lineYs) {
- const lineAnnotations = lineMap.get(lineY)!;
-
- // **6: Sort annotations in the current line left-to-right by x-coordinate.**
- lineAnnotations.sort((a, b) => a.bottom_left.x - b.bottom_left.x);
-
- // **7: Group annotations into word clusters (sentences/phrases).**
- const groupedLineAnnotations = groupWordsInSentence(lineAnnotations);
-
- // **8: Push the grouped annotations for this line into finalLines.**
- finalLines.push(groupedLineAnnotations);
- }
-
- // -------------------------
- // **First Pass**: Calculate the width of the longest line (in characters) up front.
- // We will use this to set the width of the canvas, which will reduce likelihood of collisions.
- // -------------------------
- let maxLineWidthInChars = 0;
-
- for (const line of finalLines) {
- let lineMaxEnd = 0;
- for (const ann of line) {
- // Convert normalized X to character index
- const startXInChars = Math.round(
- ann.bottom_left_normalized.x * (pageWidth / HEURISTIC_CHAR_WIDTH),
- );
- // Each annotation spans ann.text.length characters
- const endXInChars = startXInChars + ann.text.length;
-
- if (endXInChars > lineMaxEnd) {
- lineMaxEnd = endXInChars;
- }
- }
- // Track the largest width across all lines
- if (lineMaxEnd > maxLineWidthInChars) {
- maxLineWidthInChars = lineMaxEnd;
- }
- }
-
- // **9: Add a 20-char buffer to ensure we don’t cut off text.**
- maxLineWidthInChars += 20;
-
- // **10: Determine the canvas width based on the measured maxLineWidthInChars.**
- const canvasWidth = Math.max(maxLineWidthInChars, 1);
-
- // **11: Compute the baseline (lowest y) of each line to measure vertical spacing.**
- const lineBaselines = finalLines.map((line) =>
- Math.min(...line.map((a) => a.bottom_left.y)),
- );
-
- // **12: Compute the gaps between consecutive lines.**
- const verticalGaps: number[] = [];
- for (let i = 1; i < lineBaselines.length; i++) {
- verticalGaps.push(lineBaselines[i] - lineBaselines[i - 1]);
- }
-
- // **13: Estimate a "normal" line spacing via the median of these gaps.**
- const normalLineSpacing = verticalGaps.length > 0 ? median(verticalGaps) : 0;
-
- // **14: Create a 2D character canvas (array of arrays), filled with spaces.**
- let canvas: string[][] = [];
-
- // **15: lineIndex tracks which row of the canvas we’re on; start at -1 so the first line is index 0.**
- let lineIndex = -1;
-
- // **16: Render each line of text into our canvas.**
- for (let i = 0; i < finalLines.length; i++) {
- if (i === 0) {
- // **17: For the very first line, just increment lineIndex once.**
- lineIndex++;
- ensureLineExists(canvas, lineIndex, canvasWidth);
- } else {
- // **18: For subsequent lines, figure out how many blank lines to insert
- // based on the gap between this line’s baseline and the previous line’s baseline.**
- const gap = lineBaselines[i] - lineBaselines[i - 1];
-
- let extraLines = 0;
- // **19: If the gap is significantly larger than the "normal" spacing,
- // insert blank lines proportionally.**
- if (normalLineSpacing > 0 && gap > 1.2 * normalLineSpacing) {
- extraLines = Math.max(Math.round(gap / normalLineSpacing) - 1, 0);
- }
-
- // **20: Insert the calculated extra blank lines.**
- for (let e = 0; e < extraLines; e++) {
- lineIndex++;
- ensureLineExists(canvas, lineIndex, canvasWidth);
- }
-
- // **21: Move to the next line (row) in the canvas for this line’s text.**
- lineIndex++;
- ensureLineExists(canvas, lineIndex, canvasWidth);
- }
-
- // **22: Place each annotation’s text in the correct horizontal position for this line.**
- const lineAnnotations = finalLines[i];
- for (const annotation of lineAnnotations) {
- const text = annotation.text;
-
- // **23: Calculate the starting x-position in the canvas by converting normalized x to char space.**
- const startXInChars = Math.round(
- annotation.bottom_left_normalized.x *
- (pageWidth / HEURISTIC_CHAR_WIDTH),
- );
-
- // **24: Place each character of the annotation in the canvas.**
- for (let j = 0; j < text.length; j++) {
- const xPos = startXInChars + j;
- // **25: Don’t write beyond the right edge of the canvas.**
- if (xPos < canvasWidth) {
- canvas[lineIndex][xPos] = text[j];
- }
- }
- }
- }
-
- // **26: Trim trailing whitespace from each line to clean up the output.**
- canvas = canvas.map((row) => {
- const lineStr = row.join("");
- return Array.from(lineStr.trimEnd());
- });
-
- // **27: Combine all rows into a single string, separating rows with newlines.**
- let pageText = canvas.map((line) => line.join("")).join("\n");
- pageText = pageText.trimEnd();
-
- // **28: Surround the rendered text with lines of dashes for clarity.**
- pageText =
- "-".repeat(canvasWidth) + "\n" + pageText + "\n" + "-".repeat(canvasWidth);
-
- // **29: Return the final formatted text.**
- return pageText;
-}
-
-/**
- * `ensureLineExists` ensures that a specified line index exists in the canvas.
- * If the canvas is not long enough, it extends it by adding new empty lines (filled with spaces).
- * This function is used to dynamically grow the canvas as we progress through the lines.
- *
- * @param canvas - The 2D character canvas array.
- * @param lineIndex - The desired line index that must exist.
- * @param width - The width of each line in characters.
- */
-function ensureLineExists(
- canvas: string[][],
- lineIndex: number,
- width: number,
-) {
- // loop until the canvas has at least lineIndex+1 lines.
- // each new line is filled with spaces to match the required width.
- while (lineIndex >= canvas.length) {
- canvas.push(new Array(width).fill(" "));
- }
-}
-
-/**
- * `groupWordsInSentence` groups annotations within a single line into logical "words" or "sentences".
- * It uses a set of heuristics involving horizontal proximity and similar height
- * to decide when to join multiple annotations into a single grouped annotation.
- *
- * @param lineAnnotations - An array of annotations from a single line of text.
- * @returns An array of grouped annotations, where each represents one concatenated piece of text.
- */
-function groupWordsInSentence(
- lineAnnotations: TextAnnotation[],
-): TextAnnotation[] {
- const groupedAnnotations: TextAnnotation[] = [];
- let currentGroup: TextAnnotation[] = [];
-
- for (const annotation of lineAnnotations) {
- // if the current group is empty, start a new group with this annotation
- if (currentGroup.length === 0) {
- currentGroup.push(annotation);
- continue;
- }
-
- // determine horizontal grouping criteria
- // use a padding factor to allow slight spaces between words
- const padding = 1;
- const lastAnn = currentGroup[currentGroup.length - 1];
- const characterWidth = (lastAnn.width / lastAnn.text.length) * padding;
- const isWithinHorizontalRange =
- annotation.bottom_left.x <=
- lastAnn.bottom_left.x + lastAnn.width + characterWidth;
-
- // check if the annotation can be grouped with the current group.
- // conditions:
- // 1. the height difference from the group's first annotation is ≤ 4 units
- // 2. the annotation is horizontally close to the last annotation in the group
- if (
- Math.abs(annotation.height - currentGroup[0].height) <= 4 &&
- isWithinHorizontalRange
- ) {
- // if it meets the criteria, add to the current group
- currentGroup.push(annotation);
- } else {
- // if it doesn't meet criteria:
- // 1. finalize the current group into a single grouped annotation,
- // 2. add it to groupedAnnotations,
- // 3. start a new group with the current annotation
- if (currentGroup.length > 0) {
- const groupedAnnotation = createGroupedAnnotation(currentGroup);
- if (groupedAnnotation.text.length > 0) {
- groupedAnnotations.push(groupedAnnotation);
- currentGroup = [annotation];
- }
- }
- }
- }
-
- // after processing all annotations, if there's a remaining group, finalize it too
- if (currentGroup.length > 0) {
- const groupedAnnotation = createGroupedAnnotation(currentGroup);
- groupedAnnotations.push(groupedAnnotation);
- }
-
- // return the final array of grouped annotations representing words or phrases
- return groupedAnnotations;
-}
-
-/**
- * `createGroupedAnnotation` combines a group of annotations into a single annotation by concatenating their text.
- * It also attempts to preserve formatting, such as marking bold text if the median height suggests emphasis.
- *
- * @param group - An array of annotations that should be merged into a single text element.
- * @returns A new TextAnnotation representing the combined text and averaged metrics from the group.
- */
-function createGroupedAnnotation(group: TextAnnotation[]): TextAnnotation {
- // initialize an empty string to build the combined text.
- let text = "";
-
- // concatenate the text from each annotation in the group.
- // insert a space between words, except when punctuation directly follows a word
- for (const word of group) {
- if (
- [".", ",", '"', "'", ":", ";", "!", "?", "{", "}", "’", "”"].includes(
- word.text,
- )
- ) {
- text += word.text;
- } else {
- text += text !== "" ? " " + word.text : word.text;
- }
- }
-
- // determine if the combined text qualifies as a "word" (contains alphanumeric chars)
- // and whether its median height suggests emphasizing it (e.g., bold text).
- const isWord = /[a-zA-Z0-9]/.test(text);
- const medianHeight = median(group.map((word) => word.height));
-
- // if it's considered a word and tall enough, surround it with `**` for bold formatting.
- if (isWord && medianHeight > 25) {
- text = "**" + text + "**";
- }
-
- // return a new annotation that represents the merged group.
- // use the first annotation's coordinates and normalized positions as references,
- // and sum the widths of all annotations to get the total width.
- return {
- text: text,
- bottom_left: {
- x: group[0].bottom_left.x,
- y: group[0].bottom_left.y,
- },
- bottom_left_normalized: {
- x: group[0].bottom_left_normalized.x,
- y: group[0].bottom_left_normalized.y,
- },
- width: group.reduce((sum, a) => sum + a.width, 0),
- height: group[0].height,
- };
-}
-
-function median(values: number[]): number {
- if (values.length === 0) return 0;
- const sorted = [...values].sort((a, b) => a - b);
- const middle = Math.floor(sorted.length / 2);
-
- if (sorted.length % 2 === 0) {
- return (sorted[middle - 1] + sorted[middle]) / 2;
- } else {
- return sorted[middle];
- }
-}
-
-export function logLineToString(logLine: LogLine): string {
- 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}`;
- }
- return `${timestamp}::[stagehand:${logLine.category}] ${logLine.message} ${
- logLine.auxiliary ? JSON.stringify(logLine.auxiliary) : ""
- }`;
- } catch (error) {
- console.error(`Error logging line:`, error);
- return "error logging line";
- }
-}
-
-export function validateZodSchema(schema: z.ZodTypeAny, data: unknown) {
- try {
- schema.parse(data);
- return true;
- } catch {
- return false;
- }
-}
-
-export async function drawObserveOverlay(page: Page, results: ObserveResult[]) {
- // Convert single xpath to array for consistent handling
- const xpathList = results.map((result) => result.selector);
-
- // Filter out empty xpaths
- const validXpaths = xpathList.filter((xpath) => xpath !== "xpath=");
-
- await page.evaluate((selectors) => {
- selectors.forEach((selector) => {
- let element;
- if (selector.startsWith("xpath=")) {
- const xpath = selector.substring(6);
- element = document.evaluate(
- xpath,
- document,
- null,
- XPathResult.FIRST_ORDERED_NODE_TYPE,
- null,
- ).singleNodeValue;
- } else {
- element = document.querySelector(selector);
- }
-
- if (element instanceof HTMLElement) {
- const overlay = document.createElement("div");
- overlay.setAttribute("stagehandObserve", "true");
- const rect = element.getBoundingClientRect();
- overlay.style.position = "absolute";
- overlay.style.left = rect.left + "px";
- overlay.style.top = rect.top + "px";
- overlay.style.width = rect.width + "px";
- overlay.style.height = rect.height + "px";
- overlay.style.backgroundColor = "rgba(255, 255, 0, 0.3)";
- overlay.style.pointerEvents = "none";
- overlay.style.zIndex = "10000";
- document.body.appendChild(overlay);
- }
- });
- }, validXpaths);
-}
-
-export async function clearOverlays(page: Page) {
- // remove existing stagehandObserve attributes
- await page.evaluate(() => {
- const elements = document.querySelectorAll('[stagehandObserve="true"]');
- elements.forEach((el) => {
- const parent = el.parentNode;
- while (el.firstChild) {
- parent?.insertBefore(el.firstChild, el);
- }
- parent?.removeChild(el);
- });
- });
-}
-
-/**
- * Detects if the code is running in the Bun runtime environment.
- * @returns {boolean} True if running in Bun, false otherwise.
- */
-export function isRunningInBun(): boolean {
- return (
- typeof process !== "undefined" &&
- typeof process.versions !== "undefined" &&
- "bun" in process.versions
- );
-}
diff --git a/media/dark_discord.svg b/media/dark_discord.svg
new file mode 100644
index 000000000..9b50d53f9
--- /dev/null
+++ b/media/dark_discord.svg
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/media/dark_license.svg b/media/dark_license.svg
new file mode 100644
index 000000000..cbf804ea1
--- /dev/null
+++ b/media/dark_license.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/media/dark_logo.png b/media/dark_logo.png
new file mode 100644
index 000000000..4b7da66e9
Binary files /dev/null and b/media/dark_logo.png differ
diff --git a/media/director_icon.svg b/media/director_icon.svg
new file mode 100644
index 000000000..601255860
--- /dev/null
+++ b/media/director_icon.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/media/light_discord.svg b/media/light_discord.svg
new file mode 100644
index 000000000..ec0bcf462
--- /dev/null
+++ b/media/light_discord.svg
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/media/light_license.svg b/media/light_license.svg
new file mode 100644
index 000000000..a92773ef8
--- /dev/null
+++ b/media/light_license.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/media/light_logo.png b/media/light_logo.png
new file mode 100644
index 000000000..b07691e70
Binary files /dev/null and b/media/light_logo.png differ
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 53cd79748..000000000
--- a/package-lock.json
+++ /dev/null
@@ -1,10453 +0,0 @@
-{
- "name": "@browserbasehq/stagehand",
- "version": "2.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "@browserbasehq/stagehand",
- "version": "2.0.0",
- "license": "MIT",
- "dependencies": {
- "@anthropic-ai/sdk": "0.39.0",
- "@browserbasehq/sdk": "^2.4.0",
- "@google/genai": "^0.8.0",
- "openai": "^4.87.1",
- "pino": "^9.6.0",
- "pino-pretty": "^13.0.0",
- "ws": "^8.18.0",
- "zod-to-json-schema": "^3.23.5"
- },
- "devDependencies": {
- "@ai-sdk/anthropic": "^1.2.6",
- "@ai-sdk/cerebras": "^0.2.6",
- "@ai-sdk/google": "^1.2.6",
- "@ai-sdk/groq": "^1.2.4",
- "@ai-sdk/openai": "^1.0.14",
- "@ai-sdk/togetherai": "^0.2.6",
- "@changesets/changelog-github": "^0.5.0",
- "@changesets/cli": "^2.27.9",
- "@eslint/js": "^9.16.0",
- "@langchain/core": "^0.3.40",
- "@langchain/openai": "^0.4.4",
- "@types/adm-zip": "^0.5.7",
- "@types/cheerio": "^0.22.35",
- "@types/express": "^4.17.21",
- "@types/node": "^20.11.30",
- "@types/ws": "^8.5.13",
- "adm-zip": "^0.5.16",
- "ai": "^4.3.0",
- "autoevals": "^0.0.64",
- "braintrust": "^0.0.171",
- "chalk": "^5.4.1",
- "cheerio": "^1.0.0",
- "chromium-bidi": "^0.10.0",
- "esbuild": "^0.21.4",
- "eslint": "^9.16.0",
- "express": "^4.21.0",
- "globals": "^15.13.0",
- "multer": "^1.4.5-lts.1",
- "prettier": "^3.2.5",
- "string-comparison": "^1.3.0",
- "tsup": "^8.2.1",
- "tsx": "^4.10.5",
- "typescript": "^5.2.2",
- "typescript-eslint": "^8.17.0"
- },
- "peerDependencies": {
- "@playwright/test": "^1.42.1",
- "deepmerge": "^4.3.1",
- "dotenv": "^16.4.5",
- "zod": "^3.23.8"
- }
- },
- "node_modules/@ai-sdk/anthropic": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-1.2.6.tgz",
- "integrity": "sha512-Mt8ZSkhwnKHfwPPIviv3xgRE/nch2Mu4Fdh7oJDJvPDRJ6tNidCJd3TMwdlrlzPskF7hxCmXmd36yBgZZgt4cA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "@ai-sdk/provider-utils": "2.2.4"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- }
- },
- "node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz",
- "integrity": "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.4.tgz",
- "integrity": "sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "nanoid": "^3.3.8",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.23.8"
- }
- },
- "node_modules/@ai-sdk/cerebras": {
- "version": "0.2.6",
- "resolved": "https://registry.npmjs.org/@ai-sdk/cerebras/-/cerebras-0.2.6.tgz",
- "integrity": "sha512-XpVqq5462HyQPT55Ptpdb0pwti3fbLOZGDeWgxkSwJTzC+fCzDLZFLJKDCINhiMzD+8CAQPQ/qm9+inFZaF0Og==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/openai-compatible": "0.2.6",
- "@ai-sdk/provider": "1.1.0",
- "@ai-sdk/provider-utils": "2.2.4"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- }
- },
- "node_modules/@ai-sdk/cerebras/node_modules/@ai-sdk/provider": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz",
- "integrity": "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/cerebras/node_modules/@ai-sdk/provider-utils": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.4.tgz",
- "integrity": "sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "nanoid": "^3.3.8",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.23.8"
- }
- },
- "node_modules/@ai-sdk/google": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-1.2.6.tgz",
- "integrity": "sha512-e6vl+hmz7xZzWmsZZkLv89TZc19Vjgqj+RgvJNg03npRiuG4f1R/He1PD/JX6f0az//Y55CcozCcaj4vnMz6gQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "@ai-sdk/provider-utils": "2.2.4"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- }
- },
- "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz",
- "integrity": "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.4.tgz",
- "integrity": "sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "nanoid": "^3.3.8",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.23.8"
- }
- },
- "node_modules/@ai-sdk/groq": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@ai-sdk/groq/-/groq-1.2.4.tgz",
- "integrity": "sha512-jeO/tO8lGpk7L/zpPPSeZ6tMYwcXq2LbPEgmvTDhdrSj4AwYhL9WiEmZerixsKzNuOxjAzP0QZOns1SpsGaC2A==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "@ai-sdk/provider-utils": "2.2.4"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- }
- },
- "node_modules/@ai-sdk/groq/node_modules/@ai-sdk/provider": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz",
- "integrity": "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/groq/node_modules/@ai-sdk/provider-utils": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.4.tgz",
- "integrity": "sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "nanoid": "^3.3.8",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.23.8"
- }
- },
- "node_modules/@ai-sdk/openai": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.2.3.tgz",
- "integrity": "sha512-iIJKMjKYZN3XWVECDassufz3X7rq/b5BQ6Uhnp03i06T8E4QwCamwtLitJXtrqQ+OxL33Ugn3EIZKGaSBo/+qw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.0.10",
- "@ai-sdk/provider-utils": "2.1.12"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- }
- },
- "node_modules/@ai-sdk/openai-compatible": {
- "version": "0.2.6",
- "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-0.2.6.tgz",
- "integrity": "sha512-UOPEWIqG3l5K9O+p7gqiCOWzx66JtmG9v9Mab+S4E7WE34EN6u1QS1pX+RDlRDhZ0/8gNJif0r4Xlc+Ti03yNA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "@ai-sdk/provider-utils": "2.2.4"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- }
- },
- "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz",
- "integrity": "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider-utils": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.4.tgz",
- "integrity": "sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "nanoid": "^3.3.8",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.23.8"
- }
- },
- "node_modules/@ai-sdk/provider": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.0.10.tgz",
- "integrity": "sha512-pco8Zl9U0xwXI+nCLc0woMtxbvjU8hRmGTseAUiPHFLYAAL8trRPCukg69IDeinOvIeo1SmXxAIdWWPZOLb4Cg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/provider-utils": {
- "version": "2.1.12",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.1.12.tgz",
- "integrity": "sha512-NLm2Ypkv419jR5TNOvZ057ciSYFKzSDEIIwE8cRyeR1Y5RbuX+auZveqGg6GWsDzvUnn6Xra7BJmr0422v60UA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.0.10",
- "eventsource-parser": "^3.0.0",
- "nanoid": "^3.3.8",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/react": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.6.tgz",
- "integrity": "sha512-5BFChNbcYtcY9MBStcDev7WZRHf0NpTrk8yfSoedWctB3jfWkFd1HECBvdc8w3mUQshF2MumLHtAhRO7IFtGGQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider-utils": "2.2.4",
- "@ai-sdk/ui-utils": "1.2.5",
- "swr": "^2.2.5",
- "throttleit": "2.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "react": "^18 || ^19 || ^19.0.0-rc",
- "zod": "^3.23.8"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz",
- "integrity": "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider-utils": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.4.tgz",
- "integrity": "sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "nanoid": "^3.3.8",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.23.8"
- }
- },
- "node_modules/@ai-sdk/solid": {
- "version": "0.0.54",
- "resolved": "https://registry.npmjs.org/@ai-sdk/solid/-/solid-0.0.54.tgz",
- "integrity": "sha512-96KWTVK+opdFeRubqrgaJXoNiDP89gNxFRWUp0PJOotZW816AbhUf4EnDjBjXTLjXL1n0h8tGSE9sZsRkj9wQQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider-utils": "1.0.22",
- "@ai-sdk/ui-utils": "0.0.50"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "solid-js": "^1.7.7"
- },
- "peerDependenciesMeta": {
- "solid-js": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/solid/node_modules/@ai-sdk/provider": {
- "version": "0.0.26",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz",
- "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/solid/node_modules/@ai-sdk/provider-utils": {
- "version": "1.0.22",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.22.tgz",
- "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "0.0.26",
- "eventsource-parser": "^1.1.2",
- "nanoid": "^3.3.7",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/solid/node_modules/@ai-sdk/ui-utils": {
- "version": "0.0.50",
- "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.50.tgz",
- "integrity": "sha512-Z5QYJVW+5XpSaJ4jYCCAVG7zIAuKOOdikhgpksneNmKvx61ACFaf98pmOd+xnjahl0pIlc/QIe6O4yVaJ1sEaw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "0.0.26",
- "@ai-sdk/provider-utils": "1.0.22",
- "json-schema": "^0.4.0",
- "secure-json-parse": "^2.7.0",
- "zod-to-json-schema": "^3.23.3"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/solid/node_modules/eventsource-parser": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
- "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.18"
- }
- },
- "node_modules/@ai-sdk/svelte": {
- "version": "0.0.57",
- "resolved": "https://registry.npmjs.org/@ai-sdk/svelte/-/svelte-0.0.57.tgz",
- "integrity": "sha512-SyF9ItIR9ALP9yDNAD+2/5Vl1IT6kchgyDH8xkmhysfJI6WrvJbtO1wdQ0nylvPLcsPoYu+cAlz1krU4lFHcYw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider-utils": "1.0.22",
- "@ai-sdk/ui-utils": "0.0.50",
- "sswr": "^2.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0"
- },
- "peerDependenciesMeta": {
- "svelte": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/svelte/node_modules/@ai-sdk/provider": {
- "version": "0.0.26",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz",
- "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/svelte/node_modules/@ai-sdk/provider-utils": {
- "version": "1.0.22",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.22.tgz",
- "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "0.0.26",
- "eventsource-parser": "^1.1.2",
- "nanoid": "^3.3.7",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/svelte/node_modules/@ai-sdk/ui-utils": {
- "version": "0.0.50",
- "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.50.tgz",
- "integrity": "sha512-Z5QYJVW+5XpSaJ4jYCCAVG7zIAuKOOdikhgpksneNmKvx61ACFaf98pmOd+xnjahl0pIlc/QIe6O4yVaJ1sEaw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "0.0.26",
- "@ai-sdk/provider-utils": "1.0.22",
- "json-schema": "^0.4.0",
- "secure-json-parse": "^2.7.0",
- "zod-to-json-schema": "^3.23.3"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/svelte/node_modules/eventsource-parser": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
- "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.18"
- }
- },
- "node_modules/@ai-sdk/togetherai": {
- "version": "0.2.6",
- "resolved": "https://registry.npmjs.org/@ai-sdk/togetherai/-/togetherai-0.2.6.tgz",
- "integrity": "sha512-AV3CABMKlIniCe5owr6H/kSirfk3Y/MeBAetrNJxRDhmrxa5VXzbWeMxS5xeS8crqFXWJPPLcwPiwOuFtpQMrA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/openai-compatible": "0.2.6",
- "@ai-sdk/provider": "1.1.0",
- "@ai-sdk/provider-utils": "2.2.4"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- }
- },
- "node_modules/@ai-sdk/togetherai/node_modules/@ai-sdk/provider": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz",
- "integrity": "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/togetherai/node_modules/@ai-sdk/provider-utils": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.4.tgz",
- "integrity": "sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "nanoid": "^3.3.8",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.23.8"
- }
- },
- "node_modules/@ai-sdk/ui-utils": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.5.tgz",
- "integrity": "sha512-XDgqnJcaCkDez7qolvk+PDbs/ceJvgkNkxkOlc9uDWqxfDJxtvCZ+14MP/1qr4IBwGIgKVHzMDYDXvqVhSWLzg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "@ai-sdk/provider-utils": "2.2.4",
- "zod-to-json-schema": "^3.24.1"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.23.8"
- }
- },
- "node_modules/@ai-sdk/ui-utils/node_modules/@ai-sdk/provider": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz",
- "integrity": "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/ui-utils/node_modules/@ai-sdk/provider-utils": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.4.tgz",
- "integrity": "sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "nanoid": "^3.3.8",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.23.8"
- }
- },
- "node_modules/@ai-sdk/vue": {
- "version": "0.0.59",
- "resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-0.0.59.tgz",
- "integrity": "sha512-+ofYlnqdc8c4F6tM0IKF0+7NagZRAiqBJpGDJ+6EYhDW8FHLUP/JFBgu32SjxSxC6IKFZxEnl68ZoP/Z38EMlw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider-utils": "1.0.22",
- "@ai-sdk/ui-utils": "0.0.50",
- "swrv": "^1.0.4"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "vue": "^3.3.4"
- },
- "peerDependenciesMeta": {
- "vue": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/vue/node_modules/@ai-sdk/provider": {
- "version": "0.0.26",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz",
- "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@ai-sdk/vue/node_modules/@ai-sdk/provider-utils": {
- "version": "1.0.22",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.22.tgz",
- "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "0.0.26",
- "eventsource-parser": "^1.1.2",
- "nanoid": "^3.3.7",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/vue/node_modules/@ai-sdk/ui-utils": {
- "version": "0.0.50",
- "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.50.tgz",
- "integrity": "sha512-Z5QYJVW+5XpSaJ4jYCCAVG7zIAuKOOdikhgpksneNmKvx61ACFaf98pmOd+xnjahl0pIlc/QIe6O4yVaJ1sEaw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "0.0.26",
- "@ai-sdk/provider-utils": "1.0.22",
- "json-schema": "^0.4.0",
- "secure-json-parse": "^2.7.0",
- "zod-to-json-schema": "^3.23.3"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@ai-sdk/vue/node_modules/eventsource-parser": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
- "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.18"
- }
- },
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
- "license": "Apache-2.0",
- "peer": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@anthropic-ai/sdk": {
- "version": "0.39.0",
- "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.39.0.tgz",
- "integrity": "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "^18.11.18",
- "@types/node-fetch": "^2.6.4",
- "abort-controller": "^3.0.0",
- "agentkeepalive": "^4.2.1",
- "form-data-encoder": "1.7.2",
- "formdata-node": "^4.3.2",
- "node-fetch": "^2.6.7"
- }
- },
- "node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
- "version": "18.19.80",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz",
- "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~5.26.4"
- }
- },
- "node_modules/@anthropic-ai/sdk/node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
- "license": "MIT"
- },
- "node_modules/@asteasolutions/zod-to-openapi": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/@asteasolutions/zod-to-openapi/-/zod-to-openapi-6.4.0.tgz",
- "integrity": "sha512-8cxfF7AHHx2PqnN4Cd8/O8CBu/nVYJP9DpnfVLW3BFb66VJDnqI/CczZnkqMc3SNh6J9GiX7JbJ5T4BSP4HZ2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "openapi3-ts": "^4.1.2"
- },
- "peerDependencies": {
- "zod": "^3.20.2"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
- "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/types": "^7.26.10"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/runtime": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
- "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "regenerator-runtime": "^0.14.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
- "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@braintrust/core": {
- "version": "0.0.34",
- "resolved": "https://registry.npmjs.org/@braintrust/core/-/core-0.0.34.tgz",
- "integrity": "sha512-FwlBlQXDASNtCJa4VL+9roYax7W+yeK4YrpMK/D0G5gMy4vQF6MtPnkJnHVI6S1IdLa+TFBOqfzQJiKZyukhVQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@asteasolutions/zod-to-openapi": "^6.3.1",
- "uuid": "^9.0.1",
- "zod": "^3.22.4"
- }
- },
- "node_modules/@braintrust/core/node_modules/uuid": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
- "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
- "dev": true,
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
- "node_modules/@browserbasehq/sdk": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/@browserbasehq/sdk/-/sdk-2.4.0.tgz",
- "integrity": "sha512-o1+6C4MQCpZ9+8je1IUF9UvWLuSzLcBjE1kPfu2sw7MgRFNFJS1NcWCiEmjZe71ke4CNojZWaU7XTNxh8z2PUw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@types/node": "^18.11.18",
- "@types/node-fetch": "^2.6.4",
- "abort-controller": "^3.0.0",
- "agentkeepalive": "^4.2.1",
- "form-data-encoder": "1.7.2",
- "formdata-node": "^4.3.2",
- "node-fetch": "^2.6.7"
- }
- },
- "node_modules/@browserbasehq/sdk/node_modules/@types/node": {
- "version": "18.19.80",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz",
- "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~5.26.4"
- }
- },
- "node_modules/@browserbasehq/sdk/node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
- "license": "MIT"
- },
- "node_modules/@cfworker/json-schema": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz",
- "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@changesets/apply-release-plan": {
- "version": "7.0.10",
- "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.10.tgz",
- "integrity": "sha512-wNyeIJ3yDsVspYvHnEz1xQDq18D9ifed3lI+wxRQRK4pArUcuHgCTrHv0QRnnwjhVCQACxZ+CBih3wgOct6UXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/config": "^3.1.1",
- "@changesets/get-version-range-type": "^0.4.0",
- "@changesets/git": "^3.0.2",
- "@changesets/should-skip-package": "^0.1.2",
- "@changesets/types": "^6.1.0",
- "@manypkg/get-packages": "^1.1.3",
- "detect-indent": "^6.0.0",
- "fs-extra": "^7.0.1",
- "lodash.startcase": "^4.4.0",
- "outdent": "^0.5.0",
- "prettier": "^2.7.1",
- "resolve-from": "^5.0.0",
- "semver": "^7.5.3"
- }
- },
- "node_modules/@changesets/apply-release-plan/node_modules/prettier": {
- "version": "2.8.8",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
- "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "prettier": "bin-prettier.js"
- },
- "engines": {
- "node": ">=10.13.0"
- },
- "funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
- }
- },
- "node_modules/@changesets/assemble-release-plan": {
- "version": "6.0.6",
- "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.6.tgz",
- "integrity": "sha512-Frkj8hWJ1FRZiY3kzVCKzS0N5mMwWKwmv9vpam7vt8rZjLL1JMthdh6pSDVSPumHPshTTkKZ0VtNbE0cJHZZUg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/errors": "^0.2.0",
- "@changesets/get-dependents-graph": "^2.1.3",
- "@changesets/should-skip-package": "^0.1.2",
- "@changesets/types": "^6.1.0",
- "@manypkg/get-packages": "^1.1.3",
- "semver": "^7.5.3"
- }
- },
- "node_modules/@changesets/changelog-git": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz",
- "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/types": "^6.1.0"
- }
- },
- "node_modules/@changesets/changelog-github": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.5.1.tgz",
- "integrity": "sha512-BVuHtF+hrhUScSoHnJwTELB4/INQxVFc+P/Qdt20BLiBFIHFJDDUaGsZw+8fQeJTRP5hJZrzpt3oZWh0G19rAQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/get-github-info": "^0.6.0",
- "@changesets/types": "^6.1.0",
- "dotenv": "^8.1.0"
- }
- },
- "node_modules/@changesets/changelog-github/node_modules/dotenv": {
- "version": "8.6.0",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
- "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@changesets/cli": {
- "version": "2.28.1",
- "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.28.1.tgz",
- "integrity": "sha512-PiIyGRmSc6JddQJe/W1hRPjiN4VrMvb2VfQ6Uydy2punBioQrsxppyG5WafinKcW1mT0jOe/wU4k9Zy5ff21AA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/apply-release-plan": "^7.0.10",
- "@changesets/assemble-release-plan": "^6.0.6",
- "@changesets/changelog-git": "^0.2.1",
- "@changesets/config": "^3.1.1",
- "@changesets/errors": "^0.2.0",
- "@changesets/get-dependents-graph": "^2.1.3",
- "@changesets/get-release-plan": "^4.0.8",
- "@changesets/git": "^3.0.2",
- "@changesets/logger": "^0.1.1",
- "@changesets/pre": "^2.0.2",
- "@changesets/read": "^0.6.3",
- "@changesets/should-skip-package": "^0.1.2",
- "@changesets/types": "^6.1.0",
- "@changesets/write": "^0.4.0",
- "@manypkg/get-packages": "^1.1.3",
- "ansi-colors": "^4.1.3",
- "ci-info": "^3.7.0",
- "enquirer": "^2.4.1",
- "external-editor": "^3.1.0",
- "fs-extra": "^7.0.1",
- "mri": "^1.2.0",
- "p-limit": "^2.2.0",
- "package-manager-detector": "^0.2.0",
- "picocolors": "^1.1.0",
- "resolve-from": "^5.0.0",
- "semver": "^7.5.3",
- "spawndamnit": "^3.0.1",
- "term-size": "^2.1.0"
- },
- "bin": {
- "changeset": "bin.js"
- }
- },
- "node_modules/@changesets/config": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.1.tgz",
- "integrity": "sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/errors": "^0.2.0",
- "@changesets/get-dependents-graph": "^2.1.3",
- "@changesets/logger": "^0.1.1",
- "@changesets/types": "^6.1.0",
- "@manypkg/get-packages": "^1.1.3",
- "fs-extra": "^7.0.1",
- "micromatch": "^4.0.8"
- }
- },
- "node_modules/@changesets/errors": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz",
- "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "extendable-error": "^0.1.5"
- }
- },
- "node_modules/@changesets/get-dependents-graph": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz",
- "integrity": "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/types": "^6.1.0",
- "@manypkg/get-packages": "^1.1.3",
- "picocolors": "^1.1.0",
- "semver": "^7.5.3"
- }
- },
- "node_modules/@changesets/get-github-info": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.6.0.tgz",
- "integrity": "sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dataloader": "^1.4.0",
- "node-fetch": "^2.5.0"
- }
- },
- "node_modules/@changesets/get-release-plan": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.8.tgz",
- "integrity": "sha512-MM4mq2+DQU1ZT7nqxnpveDMTkMBLnwNX44cX7NSxlXmr7f8hO6/S2MXNiXG54uf/0nYnefv0cfy4Czf/ZL/EKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/assemble-release-plan": "^6.0.6",
- "@changesets/config": "^3.1.1",
- "@changesets/pre": "^2.0.2",
- "@changesets/read": "^0.6.3",
- "@changesets/types": "^6.1.0",
- "@manypkg/get-packages": "^1.1.3"
- }
- },
- "node_modules/@changesets/get-version-range-type": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz",
- "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@changesets/git": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.2.tgz",
- "integrity": "sha512-r1/Kju9Y8OxRRdvna+nxpQIsMsRQn9dhhAZt94FLDeu0Hij2hnOozW8iqnHBgvu+KdnJppCveQwK4odwfw/aWQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/errors": "^0.2.0",
- "@manypkg/get-packages": "^1.1.3",
- "is-subdir": "^1.1.1",
- "micromatch": "^4.0.8",
- "spawndamnit": "^3.0.1"
- }
- },
- "node_modules/@changesets/logger": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz",
- "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "picocolors": "^1.1.0"
- }
- },
- "node_modules/@changesets/parse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.1.tgz",
- "integrity": "sha512-iwksMs5Bf/wUItfcg+OXrEpravm5rEd9Bf4oyIPL4kVTmJQ7PNDSd6MDYkpSJR1pn7tz/k8Zf2DhTCqX08Ou+Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/types": "^6.1.0",
- "js-yaml": "^3.13.1"
- }
- },
- "node_modules/@changesets/pre": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz",
- "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/errors": "^0.2.0",
- "@changesets/types": "^6.1.0",
- "@manypkg/get-packages": "^1.1.3",
- "fs-extra": "^7.0.1"
- }
- },
- "node_modules/@changesets/read": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.3.tgz",
- "integrity": "sha512-9H4p/OuJ3jXEUTjaVGdQEhBdqoT2cO5Ts95JTFsQyawmKzpL8FnIeJSyhTDPW1MBRDnwZlHFEM9SpPwJDY5wIg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/git": "^3.0.2",
- "@changesets/logger": "^0.1.1",
- "@changesets/parse": "^0.4.1",
- "@changesets/types": "^6.1.0",
- "fs-extra": "^7.0.1",
- "p-filter": "^2.1.0",
- "picocolors": "^1.1.0"
- }
- },
- "node_modules/@changesets/should-skip-package": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz",
- "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/types": "^6.1.0",
- "@manypkg/get-packages": "^1.1.3"
- }
- },
- "node_modules/@changesets/types": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz",
- "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@changesets/write": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz",
- "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@changesets/types": "^6.1.0",
- "fs-extra": "^7.0.1",
- "human-id": "^4.1.1",
- "prettier": "^2.7.1"
- }
- },
- "node_modules/@changesets/write/node_modules/prettier": {
- "version": "2.8.8",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
- "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "prettier": "bin-prettier.js"
- },
- "engines": {
- "node": ">=10.13.0"
- },
- "funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
- }
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
- "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz",
- "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz",
- "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.0.tgz",
- "integrity": "sha512-RoV8Xs9eNwiDvhv7M+xcL4PWyRyIXRY/FLp3buU4h1EYfdF7unWUy3dOjPqb3C7rMUewIcqwW850PgS8h1o1yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
- }
- },
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/config-array": {
- "version": "0.19.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz",
- "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.6",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/config-array/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/@eslint/config-array/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/@eslint/config-helpers": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz",
- "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/core": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
- "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz",
- "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/@eslint/js": {
- "version": "9.22.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz",
- "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/object-schema": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
- "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/plugin-kit": {
- "version": "0.2.7",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz",
- "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.12.0",
- "levn": "^0.4.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@google/genai": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/@google/genai/-/genai-0.8.0.tgz",
- "integrity": "sha512-Zs+OGyZKyMbFofGJTR9/jTQSv8kITh735N3tEuIZj4VlMQXTC0soCFahysJ9NaeenRlD7xGb6fyqmX+FwrpU6Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "google-auth-library": "^9.14.2",
- "ws": "^8.18.0"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.3.0"
- },
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
- "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@kwsites/file-exists": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
- "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^4.1.1"
- }
- },
- "node_modules/@kwsites/promise-deferred": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
- "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@langchain/core": {
- "version": "0.3.42",
- "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.42.tgz",
- "integrity": "sha512-pT/jC5lqWK3YGDq8dQwgKoa6anqAhMtG1x5JbnrOj9NdaLeBbCKBDQ+/Ykzk3nZ8o+0UMsaXNZo7IVL83VVjHg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@cfworker/json-schema": "^4.0.2",
- "ansi-styles": "^5.0.0",
- "camelcase": "6",
- "decamelize": "1.2.0",
- "js-tiktoken": "^1.0.12",
- "langsmith": ">=0.2.8 <0.4.0",
- "mustache": "^4.2.0",
- "p-queue": "^6.6.2",
- "p-retry": "4",
- "uuid": "^10.0.0",
- "zod": "^3.22.4",
- "zod-to-json-schema": "^3.22.3"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@langchain/openai": {
- "version": "0.4.4",
- "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.4.4.tgz",
- "integrity": "sha512-UZybJeMd8+UX7Kn47kuFYfqKdBCeBUWNqDtmAr6ZUIMMnlsNIb6MkrEEhGgAEjGCpdT4CU8U/DyyddTz+JayOQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "js-tiktoken": "^1.0.12",
- "openai": "^4.77.0",
- "zod": "^3.22.4",
- "zod-to-json-schema": "^3.22.3"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@langchain/core": ">=0.3.39 <0.4.0"
- }
- },
- "node_modules/@manypkg/find-root": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz",
- "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.5.5",
- "@types/node": "^12.7.1",
- "find-up": "^4.1.0",
- "fs-extra": "^8.1.0"
- }
- },
- "node_modules/@manypkg/find-root/node_modules/@types/node": {
- "version": "12.20.55",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
- "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@manypkg/find-root/node_modules/fs-extra": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
- "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
- },
- "engines": {
- "node": ">=6 <7 || >=8"
- }
- },
- "node_modules/@manypkg/get-packages": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz",
- "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.5.5",
- "@changesets/types": "^4.0.1",
- "@manypkg/find-root": "^1.1.0",
- "fs-extra": "^8.1.0",
- "globby": "^11.0.0",
- "read-yaml-file": "^1.1.0"
- }
- },
- "node_modules/@manypkg/get-packages/node_modules/@changesets/types": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz",
- "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@manypkg/get-packages/node_modules/fs-extra": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
- "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
- },
- "engines": {
- "node": ">=6 <7 || >=8"
- }
- },
- "node_modules/@next/env": {
- "version": "14.2.24",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.24.tgz",
- "integrity": "sha512-LAm0Is2KHTNT6IT16lxT+suD0u+VVfYNQqM+EJTKuFRRuY2z+zj01kueWXPCxbMBDt0B5vONYzabHGUNbZYAhA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@opentelemetry/api": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
- "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@playwright/test": {
- "version": "1.51.0",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.0.tgz",
- "integrity": "sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA==",
- "license": "Apache-2.0",
- "peer": true,
- "dependencies": {
- "playwright": "1.51.0"
- },
- "bin": {
- "playwright": "cli.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz",
- "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz",
- "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz",
- "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz",
- "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz",
- "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz",
- "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz",
- "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz",
- "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz",
- "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz",
- "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz",
- "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz",
- "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz",
- "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz",
- "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz",
- "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz",
- "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz",
- "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz",
- "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz",
- "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@sveltejs/acorn-typescript": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
- "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "peerDependencies": {
- "acorn": "^8.9.0"
- }
- },
- "node_modules/@types/adm-zip": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.7.tgz",
- "integrity": "sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/body-parser": {
- "version": "1.19.5",
- "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
- "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/connect": "*",
- "@types/node": "*"
- }
- },
- "node_modules/@types/cheerio": {
- "version": "0.22.35",
- "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.35.tgz",
- "integrity": "sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/connect": {
- "version": "3.4.38",
- "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
- "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/diff-match-patch": {
- "version": "1.0.36",
- "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
- "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/express": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
- "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/body-parser": "*",
- "@types/express-serve-static-core": "^4.17.33",
- "@types/qs": "*",
- "@types/serve-static": "*"
- }
- },
- "node_modules/@types/express-serve-static-core": {
- "version": "4.19.6",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
- "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@types/qs": "*",
- "@types/range-parser": "*",
- "@types/send": "*"
- }
- },
- "node_modules/@types/http-errors": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
- "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/mime": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
- "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "20.17.24",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz",
- "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.19.2"
- }
- },
- "node_modules/@types/node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "form-data": "^4.0.0"
- }
- },
- "node_modules/@types/qs": {
- "version": "6.9.18",
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
- "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/range-parser": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
- "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/retry": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
- "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/send": {
- "version": "0.17.4",
- "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
- "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/mime": "^1",
- "@types/node": "*"
- }
- },
- "node_modules/@types/serve-static": {
- "version": "1.15.7",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
- "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/http-errors": "*",
- "@types/node": "*",
- "@types/send": "*"
- }
- },
- "node_modules/@types/uuid": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
- "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/ws": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
- "integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.26.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz",
- "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.26.1",
- "@typescript-eslint/type-utils": "8.26.1",
- "@typescript-eslint/utils": "8.26.1",
- "@typescript-eslint/visitor-keys": "8.26.1",
- "graphemer": "^1.4.0",
- "ignore": "^5.3.1",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.0.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/parser": {
- "version": "8.26.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz",
- "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.26.1",
- "@typescript-eslint/types": "8.26.1",
- "@typescript-eslint/typescript-estree": "8.26.1",
- "@typescript-eslint/visitor-keys": "8.26.1",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.26.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz",
- "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.26.1",
- "@typescript-eslint/visitor-keys": "8.26.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.26.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz",
- "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/typescript-estree": "8.26.1",
- "@typescript-eslint/utils": "8.26.1",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.0.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "8.26.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz",
- "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.26.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz",
- "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.26.1",
- "@typescript-eslint/visitor-keys": "8.26.1",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.0.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "8.26.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz",
- "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "8.26.1",
- "@typescript-eslint/types": "8.26.1",
- "@typescript-eslint/typescript-estree": "8.26.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.26.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz",
- "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.26.1",
- "eslint-visitor-keys": "^4.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@vercel/functions": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vercel/functions/-/functions-1.6.0.tgz",
- "integrity": "sha512-R6FKQrYT5MZs5IE1SqeCJWxMuBdHawFcCZboKKw8p7s+6/mcd55Gx6tWmyKnQTyrSEA04NH73Tc9CbqpEle8RA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">= 16"
- },
- "peerDependencies": {
- "@aws-sdk/credential-provider-web-identity": "*"
- },
- "peerDependenciesMeta": {
- "@aws-sdk/credential-provider-web-identity": {
- "optional": true
- }
- }
- },
- "node_modules/@vue/compiler-core": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
- "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/parser": "^7.25.3",
- "@vue/shared": "3.5.13",
- "entities": "^4.5.0",
- "estree-walker": "^2.0.2",
- "source-map-js": "^1.2.0"
- }
- },
- "node_modules/@vue/compiler-dom": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
- "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/compiler-core": "3.5.13",
- "@vue/shared": "3.5.13"
- }
- },
- "node_modules/@vue/compiler-sfc": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
- "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/parser": "^7.25.3",
- "@vue/compiler-core": "3.5.13",
- "@vue/compiler-dom": "3.5.13",
- "@vue/compiler-ssr": "3.5.13",
- "@vue/shared": "3.5.13",
- "estree-walker": "^2.0.2",
- "magic-string": "^0.30.11",
- "postcss": "^8.4.48",
- "source-map-js": "^1.2.0"
- }
- },
- "node_modules/@vue/compiler-ssr": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
- "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/compiler-dom": "3.5.13",
- "@vue/shared": "3.5.13"
- }
- },
- "node_modules/@vue/reactivity": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
- "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/shared": "3.5.13"
- }
- },
- "node_modules/@vue/runtime-core": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
- "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/reactivity": "3.5.13",
- "@vue/shared": "3.5.13"
- }
- },
- "node_modules/@vue/runtime-dom": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
- "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/reactivity": "3.5.13",
- "@vue/runtime-core": "3.5.13",
- "@vue/shared": "3.5.13",
- "csstype": "^3.1.3"
- }
- },
- "node_modules/@vue/server-renderer": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
- "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/compiler-ssr": "3.5.13",
- "@vue/shared": "3.5.13"
- },
- "peerDependencies": {
- "vue": "3.5.13"
- }
- },
- "node_modules/@vue/shared": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
- "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/abort-controller": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
- "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
- "license": "MIT",
- "dependencies": {
- "event-target-shim": "^5.0.0"
- },
- "engines": {
- "node": ">=6.5"
- }
- },
- "node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/acorn": {
- "version": "8.14.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
- "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/adm-zip": {
- "version": "0.5.16",
- "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
- "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0"
- }
- },
- "node_modules/agent-base": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
- "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
- "license": "MIT",
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/agentkeepalive": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
- "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
- "license": "MIT",
- "dependencies": {
- "humanize-ms": "^1.2.1"
- },
- "engines": {
- "node": ">= 8.0.0"
- }
- },
- "node_modules/ai": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.0.tgz",
- "integrity": "sha512-PxyQYKhWaU3LiZEpeKRaekVonZIbWdKAwgnqm0CSAxy1MFufmYEC5SM5Mc9uiK2DoHcbAL3d1jyaQ2fSDAJL8w==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "@ai-sdk/provider-utils": "2.2.4",
- "@ai-sdk/react": "1.2.6",
- "@ai-sdk/ui-utils": "1.2.5",
- "@opentelemetry/api": "1.9.0",
- "jsondiffpatch": "0.6.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "react": "^18 || ^19 || ^19.0.0-rc",
- "zod": "^3.23.8"
- },
- "peerDependenciesMeta": {
- "react": {
- "optional": true
- }
- }
- },
- "node_modules/ai/node_modules/@ai-sdk/provider": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz",
- "integrity": "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/ai/node_modules/@ai-sdk/provider-utils": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.4.tgz",
- "integrity": "sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "1.1.0",
- "nanoid": "^3.3.8",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.23.8"
- }
- },
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-colors": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
- "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/any-promise": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/append-field": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
- "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
- "license": "Python-2.0"
- },
- "node_modules/aria-query": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
- "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
- "dev": true,
- "license": "Apache-2.0",
- "peer": true,
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/array-union": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
- "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "license": "MIT"
- },
- "node_modules/atomic-sleep": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
- "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/autoevals": {
- "version": "0.0.64",
- "resolved": "https://registry.npmjs.org/autoevals/-/autoevals-0.0.64.tgz",
- "integrity": "sha512-mEv5G3Dfu5s0KH9K5M38DqXNbNBAo/1KknoE+uIRqeAdJcLAf9v6WnVE2u7SHAIRxeVZ5lVOT5fjvFqanbjEUA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@braintrust/core": "0.0.34",
- "compute-cosine-similarity": "^1.1.0",
- "js-levenshtein": "^1.1.6",
- "js-yaml": "^4.1.0",
- "linear-sum-assignment": "^1.0.7",
- "mustache": "^4.2.0",
- "openai": "4.23.0",
- "zod": "^3.22.4",
- "zod-to-json-schema": "^3.22.5"
- }
- },
- "node_modules/autoevals/node_modules/@types/node": {
- "version": "18.19.80",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz",
- "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~5.26.4"
- }
- },
- "node_modules/autoevals/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/autoevals/node_modules/openai": {
- "version": "4.23.0",
- "resolved": "https://registry.npmjs.org/openai/-/openai-4.23.0.tgz",
- "integrity": "sha512-ey2CXh1OTcTUa0AWZWuTpgA9t5GuAG3DVU1MofCRUI7fQJij8XJ3Sr0VtgxoAE69C9wbHBMCux8Z/IQZfSwHiA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/node": "^18.11.18",
- "@types/node-fetch": "^2.6.4",
- "abort-controller": "^3.0.0",
- "agentkeepalive": "^4.2.1",
- "digest-fetch": "^1.3.0",
- "form-data-encoder": "1.7.2",
- "formdata-node": "^4.3.2",
- "node-fetch": "^2.6.7",
- "web-streams-polyfill": "^3.2.1"
- },
- "bin": {
- "openai": "bin/cli"
- }
- },
- "node_modules/autoevals/node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/autoevals/node_modules/web-streams-polyfill": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
- "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/axobject-query": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
- "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
- "dev": true,
- "license": "Apache-2.0",
- "peer": true,
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/base-64": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
- "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==",
- "dev": true
- },
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/better-path-resolve": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz",
- "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-windows": "^1.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/bignumber.js": {
- "version": "9.2.1",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.2.1.tgz",
- "integrity": "sha512-+NzaKgOUvInq9TIUZ1+DRspzf/HApkCwD4btfuasFTdrfnOxqx853TgDpMolp+uv4RpRp7bPcEU2zKr9+fRmyw==",
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/binary-search": {
- "version": "1.3.6",
- "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz",
- "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==",
- "dev": true,
- "license": "CC0-1.0"
- },
- "node_modules/body-parser": {
- "version": "1.20.3",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
- "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bytes": "3.1.2",
- "content-type": "~1.0.5",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "on-finished": "2.4.1",
- "qs": "6.13.0",
- "raw-body": "2.5.2",
- "type-is": "~1.6.18",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
- "node_modules/body-parser/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/body-parser/node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/body-parser/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/boolbase": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/braintrust": {
- "version": "0.0.171",
- "resolved": "https://registry.npmjs.org/braintrust/-/braintrust-0.0.171.tgz",
- "integrity": "sha512-esL1xU9k+Ef9poUMQlFwpF7+DMzCMAihrAfcCCBWHSj8+wWGjPuAbIlegnfrT5Um8YQmiHZLnrYmChfCNVe+Ww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@ai-sdk/provider": "^0.0.11",
- "@braintrust/core": "0.0.67",
- "@next/env": "^14.2.3",
- "@vercel/functions": "^1.0.2",
- "ai": "^3.2.16",
- "argparse": "^2.0.1",
- "chalk": "^4.1.2",
- "cli-progress": "^3.12.0",
- "dotenv": "^16.4.5",
- "esbuild": "^0.18.20",
- "eventsource-parser": "^1.1.2",
- "graceful-fs": "^4.2.11",
- "minimatch": "^9.0.3",
- "mustache": "^4.2.0",
- "pluralize": "^8.0.0",
- "simple-git": "^3.21.0",
- "slugify": "^1.6.6",
- "source-map": "^0.7.4",
- "uuid": "^9.0.1",
- "zod": "^3.22.4",
- "zod-to-json-schema": "^3.22.5"
- },
- "bin": {
- "braintrust": "dist/cli.js"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- }
- },
- "node_modules/braintrust/node_modules/@ai-sdk/provider": {
- "version": "0.0.11",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.11.tgz",
- "integrity": "sha512-VTipPQ92Moa5Ovg/nZIc8yNoIFfukZjUHZcQMduJbiUh3CLQyrBAKTEV9AwjPy8wgVxj3+GZjon0yyOJKhfp5g==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/braintrust/node_modules/@ai-sdk/provider-utils": {
- "version": "1.0.22",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.22.tgz",
- "integrity": "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "0.0.26",
- "eventsource-parser": "^1.1.2",
- "nanoid": "^3.3.7",
- "secure-json-parse": "^2.7.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/braintrust/node_modules/@ai-sdk/provider-utils/node_modules/@ai-sdk/provider": {
- "version": "0.0.26",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz",
- "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/braintrust/node_modules/@ai-sdk/react": {
- "version": "0.0.70",
- "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-0.0.70.tgz",
- "integrity": "sha512-GnwbtjW4/4z7MleLiW+TOZC2M29eCg1tOUpuEiYFMmFNZK8mkrqM0PFZMo6UsYeUYMWqEOOcPOU9OQVJMJh7IQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider-utils": "1.0.22",
- "@ai-sdk/ui-utils": "0.0.50",
- "swr": "^2.2.5",
- "throttleit": "2.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "react": "^18 || ^19 || ^19.0.0-rc",
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "react": {
- "optional": true
- },
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/braintrust/node_modules/@ai-sdk/ui-utils": {
- "version": "0.0.50",
- "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.50.tgz",
- "integrity": "sha512-Z5QYJVW+5XpSaJ4jYCCAVG7zIAuKOOdikhgpksneNmKvx61ACFaf98pmOd+xnjahl0pIlc/QIe6O4yVaJ1sEaw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "0.0.26",
- "@ai-sdk/provider-utils": "1.0.22",
- "json-schema": "^0.4.0",
- "secure-json-parse": "^2.7.0",
- "zod-to-json-schema": "^3.23.3"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/braintrust/node_modules/@ai-sdk/ui-utils/node_modules/@ai-sdk/provider": {
- "version": "0.0.26",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz",
- "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/braintrust/node_modules/@braintrust/core": {
- "version": "0.0.67",
- "resolved": "https://registry.npmjs.org/@braintrust/core/-/core-0.0.67.tgz",
- "integrity": "sha512-aCWOIgGKeYeEQmU8FcPyfp0phaLpt4iaDcealooaI7Lw/Loz2LeHu5FdzVzu34B7zw3ZOkzyrr0I4X/YFdTy1w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@asteasolutions/zod-to-openapi": "^6.3.1",
- "uuid": "^9.0.1",
- "zod": "^3.22.4"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/android-arm": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
- "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/android-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
- "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/android-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
- "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/darwin-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
- "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/darwin-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
- "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
- "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/freebsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
- "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/linux-arm": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
- "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/linux-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
- "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/linux-ia32": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
- "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/linux-loong64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
- "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/linux-mips64el": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
- "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/linux-ppc64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
- "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/linux-riscv64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
- "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/linux-s390x": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
- "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/linux-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
- "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/netbsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
- "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/openbsd-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
- "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/sunos-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
- "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/win32-arm64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
- "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/win32-ia32": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
- "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/@esbuild/win32-x64": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
- "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/braintrust/node_modules/ai": {
- "version": "3.4.33",
- "resolved": "https://registry.npmjs.org/ai/-/ai-3.4.33.tgz",
- "integrity": "sha512-plBlrVZKwPoRTmM8+D1sJac9Bq8eaa2jiZlHLZIWekKWI1yMWYZvCCEezY9ASPwRhULYDJB2VhKOBUUeg3S5JQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@ai-sdk/provider": "0.0.26",
- "@ai-sdk/provider-utils": "1.0.22",
- "@ai-sdk/react": "0.0.70",
- "@ai-sdk/solid": "0.0.54",
- "@ai-sdk/svelte": "0.0.57",
- "@ai-sdk/ui-utils": "0.0.50",
- "@ai-sdk/vue": "0.0.59",
- "@opentelemetry/api": "1.9.0",
- "eventsource-parser": "1.1.2",
- "json-schema": "^0.4.0",
- "jsondiffpatch": "0.6.0",
- "secure-json-parse": "^2.7.0",
- "zod-to-json-schema": "^3.23.3"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "openai": "^4.42.0",
- "react": "^18 || ^19 || ^19.0.0-rc",
- "sswr": "^2.1.0",
- "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0",
- "zod": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "openai": {
- "optional": true
- },
- "react": {
- "optional": true
- },
- "sswr": {
- "optional": true
- },
- "svelte": {
- "optional": true
- },
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/braintrust/node_modules/ai/node_modules/@ai-sdk/provider": {
- "version": "0.0.26",
- "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.26.tgz",
- "integrity": "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "json-schema": "^0.4.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/braintrust/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/braintrust/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/braintrust/node_modules/esbuild": {
- "version": "0.18.20",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
- "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/android-arm": "0.18.20",
- "@esbuild/android-arm64": "0.18.20",
- "@esbuild/android-x64": "0.18.20",
- "@esbuild/darwin-arm64": "0.18.20",
- "@esbuild/darwin-x64": "0.18.20",
- "@esbuild/freebsd-arm64": "0.18.20",
- "@esbuild/freebsd-x64": "0.18.20",
- "@esbuild/linux-arm": "0.18.20",
- "@esbuild/linux-arm64": "0.18.20",
- "@esbuild/linux-ia32": "0.18.20",
- "@esbuild/linux-loong64": "0.18.20",
- "@esbuild/linux-mips64el": "0.18.20",
- "@esbuild/linux-ppc64": "0.18.20",
- "@esbuild/linux-riscv64": "0.18.20",
- "@esbuild/linux-s390x": "0.18.20",
- "@esbuild/linux-x64": "0.18.20",
- "@esbuild/netbsd-x64": "0.18.20",
- "@esbuild/openbsd-x64": "0.18.20",
- "@esbuild/sunos-x64": "0.18.20",
- "@esbuild/win32-arm64": "0.18.20",
- "@esbuild/win32-ia32": "0.18.20",
- "@esbuild/win32-x64": "0.18.20"
- }
- },
- "node_modules/braintrust/node_modules/eventsource-parser": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
- "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.18"
- }
- },
- "node_modules/braintrust/node_modules/uuid": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
- "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
- "dev": true,
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
- "node_modules/buffer-equal-constant-time": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
- "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/bundle-require": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz",
- "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "load-tsconfig": "^0.2.3"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "peerDependencies": {
- "esbuild": ">=0.18"
- }
- },
- "node_modules/busboy": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
- "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
- "dev": true,
- "dependencies": {
- "streamsearch": "^1.1.0"
- },
- "engines": {
- "node": ">=10.16.0"
- }
- },
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/cac": {
- "version": "6.7.14",
- "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
- "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/chalk": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
- "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.17.0 || ^14.13 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/chardet": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
- "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/charenc": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
- "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/cheerio": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
- "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cheerio-select": "^2.1.0",
- "dom-serializer": "^2.0.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.1.0",
- "encoding-sniffer": "^0.2.0",
- "htmlparser2": "^9.1.0",
- "parse5": "^7.1.2",
- "parse5-htmlparser2-tree-adapter": "^7.0.0",
- "parse5-parser-stream": "^7.1.2",
- "undici": "^6.19.5",
- "whatwg-mimetype": "^4.0.0"
- },
- "engines": {
- "node": ">=18.17"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
- }
- },
- "node_modules/cheerio-select": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
- "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "boolbase": "^1.0.0",
- "css-select": "^5.1.0",
- "css-what": "^6.1.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
- "node_modules/cheminfo-types": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/cheminfo-types/-/cheminfo-types-1.8.1.tgz",
- "integrity": "sha512-FRcpVkox+cRovffgqNdDFQ1eUav+i/Vq/CUd1hcfEl2bevntFlzznL+jE8g4twl6ElB7gZjCko6pYpXyMn+6dA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/chromium-bidi": {
- "version": "0.10.2",
- "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.10.2.tgz",
- "integrity": "sha512-hxx/MvrxMvCHriN9xb1xuVg9vthybieAe5Jikr/9iYnSdv6Q5n1WUmNG32nKoSxkG+P35lGV0hJkzXQg2bCbrQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "mitt": "3.0.1",
- "zod": "3.23.8"
- },
- "peerDependencies": {
- "devtools-protocol": "*"
- }
- },
- "node_modules/chromium-bidi/node_modules/zod": {
- "version": "3.23.8",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
- "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- },
- "node_modules/ci-info": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
- "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cli-progress": {
- "version": "3.12.0",
- "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz",
- "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "string-width": "^4.2.3"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/clsx": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/colorette": {
- "version": "2.0.20",
- "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
- "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
- "license": "MIT"
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "license": "MIT",
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/commander": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
- "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/compute-cosine-similarity": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/compute-cosine-similarity/-/compute-cosine-similarity-1.1.0.tgz",
- "integrity": "sha512-FXhNx0ILLjGi9Z9+lglLzM12+0uoTnYkHm7GiadXDAr0HGVLm25OivUS1B/LPkbzzvlcXz/1EvWg9ZYyJSdhTw==",
- "dev": true,
- "dependencies": {
- "compute-dot": "^1.1.0",
- "compute-l2norm": "^1.1.0",
- "validate.io-array": "^1.0.5",
- "validate.io-function": "^1.0.2"
- }
- },
- "node_modules/compute-dot": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/compute-dot/-/compute-dot-1.1.0.tgz",
- "integrity": "sha512-L5Ocet4DdMrXboss13K59OK23GXjiSia7+7Ukc7q4Bl+RVpIXK2W9IHMbWDZkh+JUEvJAwOKRaJDiFUa1LTnJg==",
- "dev": true,
- "dependencies": {
- "validate.io-array": "^1.0.3",
- "validate.io-function": "^1.0.2"
- }
- },
- "node_modules/compute-l2norm": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/compute-l2norm/-/compute-l2norm-1.1.0.tgz",
- "integrity": "sha512-6EHh1Elj90eU28SXi+h2PLnTQvZmkkHWySpoFz+WOlVNLz3DQoC4ISUHSV9n5jMxPHtKGJ01F4uu2PsXBB8sSg==",
- "dev": true,
- "dependencies": {
- "validate.io-array": "^1.0.3",
- "validate.io-function": "^1.0.2"
- }
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/concat-stream": {
- "version": "1.6.2",
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
- "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
- "dev": true,
- "engines": [
- "node >= 0.8"
- ],
- "license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^2.2.2",
- "typedarray": "^0.0.6"
- }
- },
- "node_modules/consola": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz",
- "integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14.18.0 || >=16.10.0"
- }
- },
- "node_modules/console-table-printer": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.12.1.tgz",
- "integrity": "sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "simple-wcswidth": "^1.0.1"
- }
- },
- "node_modules/content-disposition": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/content-type": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
- "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
- "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/core-util-is": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
- "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/crypt": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
- "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/css-select": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
- "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "boolbase": "^1.0.0",
- "css-what": "^6.1.0",
- "domhandler": "^5.0.2",
- "domutils": "^3.0.1",
- "nth-check": "^2.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
- "node_modules/css-what": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
- "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">= 6"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
- "node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/dataloader": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz",
- "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/dateformat": {
- "version": "4.6.3",
- "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
- "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/decamelize": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
- "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/dequal": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
- "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/destroy": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
- "node_modules/detect-indent": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
- "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/devtools-protocol": {
- "version": "0.0.1430640",
- "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1430640.tgz",
- "integrity": "sha512-p+uQCEoQcS5f9Zx4O0PLHz2/fPNFWA0x2tXO8rd/4U8/3/dGpl6R1gKPFHjS5Gq0L+mn7AmXYh5ude5txiAe+Q==",
- "dev": true,
- "license": "BSD-3-Clause",
- "peer": true
- },
- "node_modules/diff-match-patch": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
- "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/digest-fetch": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz",
- "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "base-64": "^0.1.0",
- "md5": "^2.3.0"
- }
- },
- "node_modules/dir-glob": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
- "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-type": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/dom-serializer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
- "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.2",
- "entities": "^4.2.0"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
- }
- },
- "node_modules/domelementtype": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
- "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "license": "BSD-2-Clause"
- },
- "node_modules/domhandler": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
- "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "domelementtype": "^2.3.0"
- },
- "engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
- }
- },
- "node_modules/domutils": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
- "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "dom-serializer": "^2.0.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3"
- },
- "funding": {
- "url": "https://github.com/fb55/domutils?sponsor=1"
- }
- },
- "node_modules/dotenv": {
- "version": "16.4.7",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
- "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://dotenvx.com"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/ecdsa-sig-formatter": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
- "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/encoding-sniffer": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
- "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "iconv-lite": "^0.6.3",
- "whatwg-encoding": "^3.1.1"
- },
- "funding": {
- "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
- }
- },
- "node_modules/end-of-stream": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
- "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
- "license": "MIT",
- "dependencies": {
- "once": "^1.4.0"
- }
- },
- "node_modules/enquirer": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
- "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-colors": "^4.1.1",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/esbuild": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
- "@esbuild/darwin-arm64": "0.21.5",
- "@esbuild/darwin-x64": "0.21.5",
- "@esbuild/freebsd-arm64": "0.21.5",
- "@esbuild/freebsd-x64": "0.21.5",
- "@esbuild/linux-arm": "0.21.5",
- "@esbuild/linux-arm64": "0.21.5",
- "@esbuild/linux-ia32": "0.21.5",
- "@esbuild/linux-loong64": "0.21.5",
- "@esbuild/linux-mips64el": "0.21.5",
- "@esbuild/linux-ppc64": "0.21.5",
- "@esbuild/linux-riscv64": "0.21.5",
- "@esbuild/linux-s390x": "0.21.5",
- "@esbuild/linux-x64": "0.21.5",
- "@esbuild/netbsd-x64": "0.21.5",
- "@esbuild/openbsd-x64": "0.21.5",
- "@esbuild/sunos-x64": "0.21.5",
- "@esbuild/win32-arm64": "0.21.5",
- "@esbuild/win32-ia32": "0.21.5",
- "@esbuild/win32-x64": "0.21.5"
- }
- },
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint": {
- "version": "9.22.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz",
- "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.19.2",
- "@eslint/config-helpers": "^0.1.0",
- "@eslint/core": "^0.12.0",
- "@eslint/eslintrc": "^3.3.0",
- "@eslint/js": "9.22.0",
- "@eslint/plugin-kit": "^0.2.7",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "@types/json-schema": "^7.0.15",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.3.0",
- "eslint-visitor-keys": "^4.2.0",
- "espree": "^10.3.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
- "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/eslint/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/eslint/node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint/node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/eslint/node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint/node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/esm-env": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
- "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/espree": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.14.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrap": {
- "version": "1.4.5",
- "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.5.tgz",
- "integrity": "sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estree-walker": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/event-target-shim": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
- "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/eventemitter3": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
- "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/eventsource-parser": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz",
- "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/express": {
- "version": "4.21.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
- "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "accepts": "~1.3.8",
- "array-flatten": "1.1.1",
- "body-parser": "1.20.3",
- "content-disposition": "0.5.4",
- "content-type": "~1.0.4",
- "cookie": "0.7.1",
- "cookie-signature": "1.0.6",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "1.3.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
- "merge-descriptors": "1.0.3",
- "methods": "~1.1.2",
- "on-finished": "2.4.1",
- "parseurl": "~1.3.3",
- "path-to-regexp": "0.1.12",
- "proxy-addr": "~2.0.7",
- "qs": "6.13.0",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.2.1",
- "send": "0.19.0",
- "serve-static": "1.16.2",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
- },
- "engines": {
- "node": ">= 0.10.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/express/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/express/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/extend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "license": "MIT"
- },
- "node_modules/extendable-error": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz",
- "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/external-editor": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
- "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chardet": "^0.7.0",
- "iconv-lite": "^0.4.24",
- "tmp": "^0.0.33"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/external-editor/node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/fast-copy": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz",
- "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==",
- "license": "MIT"
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-redact": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
- "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/fast-safe-stringify": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
- "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
- "license": "MIT"
- },
- "node_modules/fastq": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
- "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/fft.js": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/fft.js/-/fft.js-4.0.4.tgz",
- "integrity": "sha512-f9c00hphOgeQTlDyavwTtu6RiK8AIFjD6+jvXkNkpeQ7rirK3uFWVpalkoS4LAwbdX7mfZ8aoBfFVQX1Re/8aw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/finalhandler": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
- "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "2.6.9",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "on-finished": "2.4.1",
- "parseurl": "~1.3.3",
- "statuses": "2.0.1",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/finalhandler/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/finalhandler/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/foreground-child": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
- "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.6",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/form-data": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
- "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/form-data-encoder": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
- "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
- "license": "MIT"
- },
- "node_modules/formdata-node": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
- "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
- "license": "MIT",
- "dependencies": {
- "node-domexception": "1.0.0",
- "web-streams-polyfill": "4.0.0-beta.3"
- },
- "engines": {
- "node": ">= 12.20"
- }
- },
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fs-extra": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
- "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.1.2",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
- },
- "engines": {
- "node": ">=6 <7 || >=8"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gaxios": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
- "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "extend": "^3.0.2",
- "https-proxy-agent": "^7.0.1",
- "is-stream": "^2.0.0",
- "node-fetch": "^2.6.9",
- "uuid": "^9.0.1"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/gaxios/node_modules/uuid": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
- "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
- "node_modules/gcp-metadata": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
- "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
- "license": "Apache-2.0",
- "dependencies": {
- "gaxios": "^6.1.1",
- "google-logging-utils": "^0.0.2",
- "json-bigint": "^1.0.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/get-tsconfig": {
- "version": "4.10.0",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz",
- "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "resolve-pkg-maps": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
- }
- },
- "node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/globals": {
- "version": "15.15.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
- "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/globby": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
- "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-union": "^2.1.0",
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.9",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/google-auth-library": {
- "version": "9.15.1",
- "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
- "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
- "license": "Apache-2.0",
- "dependencies": {
- "base64-js": "^1.3.0",
- "ecdsa-sig-formatter": "^1.0.11",
- "gaxios": "^6.1.1",
- "gcp-metadata": "^6.1.0",
- "gtoken": "^7.0.0",
- "jws": "^4.0.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/google-logging-utils": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
- "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/gtoken": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
- "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
- "license": "MIT",
- "dependencies": {
- "gaxios": "^6.0.0",
- "jws": "^4.0.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "license": "MIT",
- "dependencies": {
- "has-symbols": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/help-me": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
- "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
- "license": "MIT"
- },
- "node_modules/htmlparser2": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
- "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
- "dev": true,
- "funding": [
- "https://github.com/fb55/htmlparser2?sponsor=1",
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.1.0",
- "entities": "^4.5.0"
- }
- },
- "node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
- "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/human-id": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.1.tgz",
- "integrity": "sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "human-id": "dist/cli.js"
- }
- },
- "node_modules/humanize-ms": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
- "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.0.0"
- }
- },
- "node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/import-fresh/node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/install": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz",
- "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/is-any-array": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.1.tgz",
- "integrity": "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/is-buffer": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
- "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/is-reference": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
- "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/estree": "^1.0.6"
- }
- },
- "node_modules/is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-subdir": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz",
- "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "better-path-resolve": "1.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/is-windows": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
- "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
- "node_modules/joycon": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
- "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/js-levenshtein": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
- "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/js-tiktoken": {
- "version": "1.0.19",
- "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.19.tgz",
- "integrity": "sha512-XC63YQeEcS47Y53gg950xiZ4IWmkfMe4p2V9OSaBt26q+p47WHn18izuXzSclCI73B7yGqtfRsT6jcZQI0y08g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.5.1"
- }
- },
- "node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/js-yaml/node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "sprintf-js": "~1.0.2"
- }
- },
- "node_modules/json-bigint": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
- "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
- "license": "MIT",
- "dependencies": {
- "bignumber.js": "^9.0.0"
- }
- },
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-schema": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
- "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
- "dev": true,
- "license": "(AFL-2.1 OR BSD-3-Clause)"
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/jsondiffpatch": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
- "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/diff-match-patch": "^1.0.36",
- "chalk": "^5.3.0",
- "diff-match-patch": "^1.0.5"
- },
- "bin": {
- "jsondiffpatch": "bin/jsondiffpatch.js"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- }
- },
- "node_modules/jsonfile": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
- "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
- "dev": true,
- "license": "MIT",
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
- }
- },
- "node_modules/jwa": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
- "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
- "license": "MIT",
- "dependencies": {
- "buffer-equal-constant-time": "1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/jws": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
- "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
- "license": "MIT",
- "dependencies": {
- "jwa": "^2.0.0",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/langsmith": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.13.tgz",
- "integrity": "sha512-iI5MO5FP1EFxU1jyQvB2cM4qKqXXwnsd124MKWWhBuV2O/EjdwzsyKPBVlBPFjAQbgCGtzqdJWbv9xld60hb+Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/uuid": "^10.0.0",
- "chalk": "^4.1.2",
- "console-table-printer": "^2.12.1",
- "p-queue": "^6.6.2",
- "p-retry": "4",
- "semver": "^7.6.3",
- "uuid": "^10.0.0"
- },
- "peerDependencies": {
- "openai": "*"
- },
- "peerDependenciesMeta": {
- "openai": {
- "optional": true
- }
- }
- },
- "node_modules/langsmith/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/langsmith/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/lilconfig": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
- "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/antonk52"
- }
- },
- "node_modules/linear-sum-assignment": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/linear-sum-assignment/-/linear-sum-assignment-1.0.7.tgz",
- "integrity": "sha512-jfLoSGwZNyjfY8eK4ayhjfcIu3BfWvP6sWieYzYI3AWldwXVoWEz1gtrQL10v/8YltYLBunqNjeVFXPMUs+MJg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cheminfo-types": "^1.7.3",
- "install": "^0.13.0",
- "ml-matrix": "^6.11.0",
- "ml-spectra-processing": "^14.2.2"
- }
- },
- "node_modules/lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/load-tsconfig": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz",
- "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- }
- },
- "node_modules/locate-character": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
- "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lodash.startcase": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
- "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/magic-string": {
- "version": "0.30.17",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
- "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0"
- }
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/md5": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
- "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "charenc": "0.0.2",
- "crypt": "0.0.2",
- "is-buffer": "~1.1.6"
- }
- },
- "node_modules/media-typer": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/merge-descriptors": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
- "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/mitt": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
- "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/mkdirp": {
- "version": "0.5.6",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
- "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "minimist": "^1.2.6"
- },
- "bin": {
- "mkdirp": "bin/cmd.js"
- }
- },
- "node_modules/ml-array-max": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/ml-array-max/-/ml-array-max-1.2.4.tgz",
- "integrity": "sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-any-array": "^2.0.0"
- }
- },
- "node_modules/ml-array-min": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/ml-array-min/-/ml-array-min-1.2.3.tgz",
- "integrity": "sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-any-array": "^2.0.0"
- }
- },
- "node_modules/ml-array-rescale": {
- "version": "1.3.7",
- "resolved": "https://registry.npmjs.org/ml-array-rescale/-/ml-array-rescale-1.3.7.tgz",
- "integrity": "sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-any-array": "^2.0.0",
- "ml-array-max": "^1.2.4",
- "ml-array-min": "^1.2.3"
- }
- },
- "node_modules/ml-matrix": {
- "version": "6.12.1",
- "resolved": "https://registry.npmjs.org/ml-matrix/-/ml-matrix-6.12.1.tgz",
- "integrity": "sha512-TJ+8eOFdp+INvzR4zAuwBQJznDUfktMtOB6g/hUcGh3rcyjxbz4Te57Pgri8Q9bhSQ7Zys4IYOGhFdnlgeB6Lw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-any-array": "^2.0.1",
- "ml-array-rescale": "^1.3.7"
- }
- },
- "node_modules/ml-spectra-processing": {
- "version": "14.10.0",
- "resolved": "https://registry.npmjs.org/ml-spectra-processing/-/ml-spectra-processing-14.10.0.tgz",
- "integrity": "sha512-4fyF6tojgVgh6m9nmFvaIlGhrvHq+swn64IxQ44F4k4o7Qkl8xKOJWfQ4EsfoX66GqZn2PFfcn1xUGRNwB8+3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "binary-search": "^1.3.6",
- "cheminfo-types": "^1.8.1",
- "fft.js": "^4.0.4",
- "is-any-array": "^2.0.1",
- "ml-matrix": "^6.12.0",
- "ml-xsadd": "^3.0.1"
- }
- },
- "node_modules/ml-xsadd": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ml-xsadd/-/ml-xsadd-3.0.1.tgz",
- "integrity": "sha512-Fz2q6dwgzGM8wYKGArTUTZDGa4lQFA2Vi6orjGeTVRy22ZnQFKlJuwS9n8NRviqz1KHAHAzdKJwbnYhdo38uYg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/mri": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
- "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/multer": {
- "version": "1.4.5-lts.1",
- "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
- "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "append-field": "^1.0.0",
- "busboy": "^1.0.0",
- "concat-stream": "^1.5.2",
- "mkdirp": "^0.5.4",
- "object-assign": "^4.1.1",
- "type-is": "^1.6.4",
- "xtend": "^4.0.0"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
- "node_modules/mustache": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
- "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "mustache": "bin/mustache"
- }
- },
- "node_modules/mz": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
- "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0",
- "object-assign": "^4.0.1",
- "thenify-all": "^1.0.0"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.9",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz",
- "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/node-domexception": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "github",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=10.5.0"
- }
- },
- "node_modules/node-fetch": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
- "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
- "license": "MIT",
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/nth-check": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
- "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "boolbase": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/fb55/nth-check?sponsor=1"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/on-exit-leak-free": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
- "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/on-finished": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/openai": {
- "version": "4.87.3",
- "resolved": "https://registry.npmjs.org/openai/-/openai-4.87.3.tgz",
- "integrity": "sha512-d2D54fzMuBYTxMW8wcNmhT1rYKcTfMJ8t+4KjH2KtvYenygITiGBgHoIrzHwnDQWW+C5oCA+ikIR2jgPCFqcKQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@types/node": "^18.11.18",
- "@types/node-fetch": "^2.6.4",
- "abort-controller": "^3.0.0",
- "agentkeepalive": "^4.2.1",
- "form-data-encoder": "1.7.2",
- "formdata-node": "^4.3.2",
- "node-fetch": "^2.6.7"
- },
- "bin": {
- "openai": "bin/cli"
- },
- "peerDependencies": {
- "ws": "^8.18.0",
- "zod": "^3.23.8"
- },
- "peerDependenciesMeta": {
- "ws": {
- "optional": true
- },
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/openai/node_modules/@types/node": {
- "version": "18.19.80",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz",
- "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~5.26.4"
- }
- },
- "node_modules/openai/node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
- "license": "MIT"
- },
- "node_modules/openapi3-ts": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-4.4.0.tgz",
- "integrity": "sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yaml": "^2.5.0"
- }
- },
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/os-tmpdir": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
- "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/outdent": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz",
- "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/p-filter": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz",
- "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-map": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/p-finally": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
- "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/p-map": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
- "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/p-queue": {
- "version": "6.6.2",
- "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
- "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eventemitter3": "^4.0.4",
- "p-timeout": "^3.2.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-retry": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
- "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/retry": "0.12.0",
- "retry": "^0.13.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/p-timeout": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
- "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-finally": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/p-try": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
- "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
- "node_modules/package-manager-detector": {
- "version": "0.2.11",
- "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz",
- "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "quansync": "^0.2.7"
- }
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/parse5": {
- "version": "7.2.1",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
- "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "entities": "^4.5.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/parse5-htmlparser2-tree-adapter": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
- "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "domhandler": "^5.0.3",
- "parse5": "^7.0.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/parse5-parser-stream": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
- "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parse5": "^7.0.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/path-to-regexp": {
- "version": "0.1.12",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
- "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/path-type": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
- "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/pify": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
- "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/pino": {
- "version": "9.6.0",
- "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz",
- "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==",
- "license": "MIT",
- "dependencies": {
- "atomic-sleep": "^1.0.0",
- "fast-redact": "^3.1.1",
- "on-exit-leak-free": "^2.1.0",
- "pino-abstract-transport": "^2.0.0",
- "pino-std-serializers": "^7.0.0",
- "process-warning": "^4.0.0",
- "quick-format-unescaped": "^4.0.3",
- "real-require": "^0.2.0",
- "safe-stable-stringify": "^2.3.1",
- "sonic-boom": "^4.0.1",
- "thread-stream": "^3.0.0"
- },
- "bin": {
- "pino": "bin.js"
- }
- },
- "node_modules/pino-abstract-transport": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
- "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
- "license": "MIT",
- "dependencies": {
- "split2": "^4.0.0"
- }
- },
- "node_modules/pino-pretty": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.0.0.tgz",
- "integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==",
- "license": "MIT",
- "dependencies": {
- "colorette": "^2.0.7",
- "dateformat": "^4.6.3",
- "fast-copy": "^3.0.2",
- "fast-safe-stringify": "^2.1.1",
- "help-me": "^5.0.0",
- "joycon": "^3.1.1",
- "minimist": "^1.2.6",
- "on-exit-leak-free": "^2.1.0",
- "pino-abstract-transport": "^2.0.0",
- "pump": "^3.0.0",
- "secure-json-parse": "^2.4.0",
- "sonic-boom": "^4.0.1",
- "strip-json-comments": "^3.1.1"
- },
- "bin": {
- "pino-pretty": "bin.js"
- }
- },
- "node_modules/pino-std-serializers": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
- "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==",
- "license": "MIT"
- },
- "node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/playwright": {
- "version": "1.51.0",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.0.tgz",
- "integrity": "sha512-442pTfGM0xxfCYxuBa/Pu6B2OqxqqaYq39JS8QDMGThUvIOCd6s0ANDog3uwA0cHavVlnTQzGCN7Id2YekDSXA==",
- "license": "Apache-2.0",
- "peer": true,
- "dependencies": {
- "playwright-core": "1.51.0"
- },
- "bin": {
- "playwright": "cli.js"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "fsevents": "2.3.2"
- }
- },
- "node_modules/playwright-core": {
- "version": "1.51.0",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.0.tgz",
- "integrity": "sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg==",
- "license": "Apache-2.0",
- "peer": true,
- "bin": {
- "playwright-core": "cli.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/pluralize": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
- "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.3",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
- "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "nanoid": "^3.3.8",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/postcss-load-config": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
- "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "lilconfig": "^3.1.1"
- },
- "engines": {
- "node": ">= 18"
- },
- "peerDependencies": {
- "jiti": ">=1.21.0",
- "postcss": ">=8.0.9",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- },
- "postcss": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/prettier": {
- "version": "3.5.3",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
- "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "prettier": "bin/prettier.cjs"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
- }
- },
- "node_modules/process-nextick-args": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/process-warning": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz",
- "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT"
- },
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/pump": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
- "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/qs": {
- "version": "6.13.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
- "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.0.6"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/quansync": {
- "version": "0.2.8",
- "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.8.tgz",
- "integrity": "sha512-4+saucphJMazjt7iOM27mbFCk+D9dd/zmgMDCzRZ8MEoBfYp7lAvoN38et/phRQF6wOPMy/OROBGgoWeSKyluA==",
- "dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/antfu"
- },
- {
- "type": "individual",
- "url": "https://github.com/sponsors/sxzz"
- }
- ],
- "license": "MIT"
- },
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/quick-format-unescaped": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
- "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
- "license": "MIT"
- },
- "node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/raw-body": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
- "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bytes": "3.1.2",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/raw-body/node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react": {
- "version": "19.0.0",
- "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
- "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/read-yaml-file": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz",
- "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.1.5",
- "js-yaml": "^3.6.1",
- "pify": "^4.0.1",
- "strip-bom": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/readable-stream": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
- "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "node_modules/readable-stream/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/real-require": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
- "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
- "license": "MIT",
- "engines": {
- "node": ">= 12.13.0"
- }
- },
- "node_modules/regenerator-runtime": {
- "version": "0.14.1",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/resolve-pkg-maps": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
- "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
- }
- },
- "node_modules/retry": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
- "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/reusify": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
- "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/rollup": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz",
- "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "1.0.6"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.35.0",
- "@rollup/rollup-android-arm64": "4.35.0",
- "@rollup/rollup-darwin-arm64": "4.35.0",
- "@rollup/rollup-darwin-x64": "4.35.0",
- "@rollup/rollup-freebsd-arm64": "4.35.0",
- "@rollup/rollup-freebsd-x64": "4.35.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.35.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.35.0",
- "@rollup/rollup-linux-arm64-gnu": "4.35.0",
- "@rollup/rollup-linux-arm64-musl": "4.35.0",
- "@rollup/rollup-linux-loongarch64-gnu": "4.35.0",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.35.0",
- "@rollup/rollup-linux-s390x-gnu": "4.35.0",
- "@rollup/rollup-linux-x64-gnu": "4.35.0",
- "@rollup/rollup-linux-x64-musl": "4.35.0",
- "@rollup/rollup-win32-arm64-msvc": "4.35.0",
- "@rollup/rollup-win32-ia32-msvc": "4.35.0",
- "@rollup/rollup-win32-x64-msvc": "4.35.0",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/safe-stable-stringify": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
- "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/secure-json-parse": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
- "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==",
- "license": "BSD-3-Clause"
- },
- "node_modules/semver": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
- "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/send": {
- "version": "0.19.0",
- "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
- "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
- "mime": "1.6.0",
- "ms": "2.1.3",
- "on-finished": "2.4.1",
- "range-parser": "~1.2.1",
- "statuses": "2.0.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/send/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/send/node_modules/debug/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/send/node_modules/encodeurl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/serve-static": {
- "version": "1.16.2",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
- "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.19.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/simple-git": {
- "version": "3.27.0",
- "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz",
- "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@kwsites/file-exists": "^1.1.1",
- "@kwsites/promise-deferred": "^1.1.1",
- "debug": "^4.3.5"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/steveukx/git-js?sponsor=1"
- }
- },
- "node_modules/simple-wcswidth": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz",
- "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/slugify": {
- "version": "1.6.6",
- "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
- "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/sonic-boom": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
- "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
- "license": "MIT",
- "dependencies": {
- "atomic-sleep": "^1.0.0"
- }
- },
- "node_modules/source-map": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
- "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/spawndamnit": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz",
- "integrity": "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==",
- "dev": true,
- "license": "SEE LICENSE IN LICENSE",
- "dependencies": {
- "cross-spawn": "^7.0.5",
- "signal-exit": "^4.0.1"
- }
- },
- "node_modules/split2": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
- "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
- "license": "ISC",
- "engines": {
- "node": ">= 10.x"
- }
- },
- "node_modules/sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/sswr": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/sswr/-/sswr-2.2.0.tgz",
- "integrity": "sha512-clTszLPZkmycALTHD1mXGU+mOtA/MIoLgS1KGTTzFNVm9rytQVykgRaP+z1zl572cz0bTqj4rFVoC2N+IGK4Sg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "swrev": "^4.0.0"
- },
- "peerDependencies": {
- "svelte": "^4.0.0 || ^5.0.0"
- }
- },
- "node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/streamsearch": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
- "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
- "dev": true,
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.1.0"
- }
- },
- "node_modules/string_decoder/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/string-comparison": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string-comparison/-/string-comparison-1.3.0.tgz",
- "integrity": "sha512-46aD+slEwybxAMPRII83ATbgMgTiz5P8mVd7Z6VJsCzSHFjdt1hkAVLeFxPIyEb11tc6ihpJTlIqoO0MCF6NPw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- }
- },
- "node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/sucrase": {
- "version": "3.35.0",
- "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
- "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.2",
- "commander": "^4.0.0",
- "glob": "^10.3.10",
- "lines-and-columns": "^1.1.6",
- "mz": "^2.7.0",
- "pirates": "^4.0.1",
- "ts-interface-checker": "^0.1.9"
- },
- "bin": {
- "sucrase": "bin/sucrase",
- "sucrase-node": "bin/sucrase-node"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/svelte": {
- "version": "5.23.0",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.23.0.tgz",
- "integrity": "sha512-v0lL3NuKontiCxholEiAXCB+BYbndlKbwlDMK0DS86WgGELMJSpyqCSbJeMEMBDwOglnS7Ar2Rq0wwa/z2L8Vg==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@ampproject/remapping": "^2.3.0",
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@sveltejs/acorn-typescript": "^1.0.5",
- "@types/estree": "^1.0.5",
- "acorn": "^8.12.1",
- "aria-query": "^5.3.1",
- "axobject-query": "^4.1.0",
- "clsx": "^2.1.1",
- "esm-env": "^1.2.1",
- "esrap": "^1.4.3",
- "is-reference": "^3.0.3",
- "locate-character": "^3.0.0",
- "magic-string": "^0.30.11",
- "zimmerframe": "^1.1.2"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/swr": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz",
- "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dequal": "^2.0.3",
- "use-sync-external-store": "^1.4.0"
- },
- "peerDependencies": {
- "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/swrev": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/swrev/-/swrev-4.0.0.tgz",
- "integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/swrv": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.1.0.tgz",
- "integrity": "sha512-pjllRDr2s0iTwiE5Isvip51dZGR7GjLH1gCSVyE8bQnbAx6xackXsFdojau+1O5u98yHF5V73HQGOFxKUXO9gQ==",
- "dev": true,
- "license": "Apache-2.0",
- "peerDependencies": {
- "vue": ">=3.2.26 < 4"
- }
- },
- "node_modules/term-size": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
- "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/thenify": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
- "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0"
- }
- },
- "node_modules/thenify-all": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
- "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "thenify": ">= 3.1.0 < 4"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/thread-stream": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
- "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
- "license": "MIT",
- "dependencies": {
- "real-require": "^0.2.0"
- }
- },
- "node_modules/throttleit": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
- "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/tinyexec": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
- "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tinyglobby": {
- "version": "0.2.12",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
- "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.4.3",
- "picomatch": "^4.0.2"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.4.3",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
- "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/tmp": {
- "version": "0.0.33",
- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
- "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "os-tmpdir": "~1.0.2"
- },
- "engines": {
- "node": ">=0.6.0"
- }
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/tr46": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.0.tgz",
- "integrity": "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==",
- "license": "MIT",
- "dependencies": {
- "punycode": "^2.3.1"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tree-kill": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
- "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "tree-kill": "cli.js"
- }
- },
- "node_modules/ts-api-utils": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
- "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.12"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4"
- }
- },
- "node_modules/ts-interface-checker": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
- "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/tsup": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.4.0.tgz",
- "integrity": "sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bundle-require": "^5.1.0",
- "cac": "^6.7.14",
- "chokidar": "^4.0.3",
- "consola": "^3.4.0",
- "debug": "^4.4.0",
- "esbuild": "^0.25.0",
- "joycon": "^3.1.1",
- "picocolors": "^1.1.1",
- "postcss-load-config": "^6.0.1",
- "resolve-from": "^5.0.0",
- "rollup": "^4.34.8",
- "source-map": "0.8.0-beta.0",
- "sucrase": "^3.35.0",
- "tinyexec": "^0.3.2",
- "tinyglobby": "^0.2.11",
- "tree-kill": "^1.2.2"
- },
- "bin": {
- "tsup": "dist/cli-default.js",
- "tsup-node": "dist/cli-node.js"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@microsoft/api-extractor": "^7.36.0",
- "@swc/core": "^1",
- "postcss": "^8.4.12",
- "typescript": ">=4.5.0"
- },
- "peerDependenciesMeta": {
- "@microsoft/api-extractor": {
- "optional": true
- },
- "@swc/core": {
- "optional": true
- },
- "postcss": {
- "optional": true
- },
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/tsup/node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
- "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/android-arm": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
- "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/android-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
- "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/android-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
- "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
- "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/darwin-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
- "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
- "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
- "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/linux-arm": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
- "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/linux-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
- "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/linux-ia32": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
- "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/linux-loong64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
- "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
- "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
- "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
- "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/linux-s390x": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
- "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/linux-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
- "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
- "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
- "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/sunos-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
- "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/win32-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
- "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/win32-ia32": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
- "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/@esbuild/win32-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
- "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsup/node_modules/esbuild": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
- "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.1",
- "@esbuild/android-arm": "0.25.1",
- "@esbuild/android-arm64": "0.25.1",
- "@esbuild/android-x64": "0.25.1",
- "@esbuild/darwin-arm64": "0.25.1",
- "@esbuild/darwin-x64": "0.25.1",
- "@esbuild/freebsd-arm64": "0.25.1",
- "@esbuild/freebsd-x64": "0.25.1",
- "@esbuild/linux-arm": "0.25.1",
- "@esbuild/linux-arm64": "0.25.1",
- "@esbuild/linux-ia32": "0.25.1",
- "@esbuild/linux-loong64": "0.25.1",
- "@esbuild/linux-mips64el": "0.25.1",
- "@esbuild/linux-ppc64": "0.25.1",
- "@esbuild/linux-riscv64": "0.25.1",
- "@esbuild/linux-s390x": "0.25.1",
- "@esbuild/linux-x64": "0.25.1",
- "@esbuild/netbsd-arm64": "0.25.1",
- "@esbuild/netbsd-x64": "0.25.1",
- "@esbuild/openbsd-arm64": "0.25.1",
- "@esbuild/openbsd-x64": "0.25.1",
- "@esbuild/sunos-x64": "0.25.1",
- "@esbuild/win32-arm64": "0.25.1",
- "@esbuild/win32-ia32": "0.25.1",
- "@esbuild/win32-x64": "0.25.1"
- }
- },
- "node_modules/tsup/node_modules/source-map": {
- "version": "0.8.0-beta.0",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz",
- "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "whatwg-url": "^7.0.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/tsx": {
- "version": "4.19.3",
- "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz",
- "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "~0.25.0",
- "get-tsconfig": "^4.7.5"
- },
- "bin": {
- "tsx": "dist/cli.mjs"
- },
- "engines": {
- "node": ">=18.0.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
- "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/android-arm": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
- "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/android-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
- "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/android-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
- "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
- "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
- "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
- "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
- "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-arm": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
- "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
- "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
- "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
- "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
- "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
- "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
- "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
- "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/linux-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
- "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
- "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
- "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
- "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
- "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
- "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/@esbuild/win32-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
- "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tsx/node_modules/esbuild": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
- "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.1",
- "@esbuild/android-arm": "0.25.1",
- "@esbuild/android-arm64": "0.25.1",
- "@esbuild/android-x64": "0.25.1",
- "@esbuild/darwin-arm64": "0.25.1",
- "@esbuild/darwin-x64": "0.25.1",
- "@esbuild/freebsd-arm64": "0.25.1",
- "@esbuild/freebsd-x64": "0.25.1",
- "@esbuild/linux-arm": "0.25.1",
- "@esbuild/linux-arm64": "0.25.1",
- "@esbuild/linux-ia32": "0.25.1",
- "@esbuild/linux-loong64": "0.25.1",
- "@esbuild/linux-mips64el": "0.25.1",
- "@esbuild/linux-ppc64": "0.25.1",
- "@esbuild/linux-riscv64": "0.25.1",
- "@esbuild/linux-s390x": "0.25.1",
- "@esbuild/linux-x64": "0.25.1",
- "@esbuild/netbsd-arm64": "0.25.1",
- "@esbuild/netbsd-x64": "0.25.1",
- "@esbuild/openbsd-arm64": "0.25.1",
- "@esbuild/openbsd-x64": "0.25.1",
- "@esbuild/sunos-x64": "0.25.1",
- "@esbuild/win32-arm64": "0.25.1",
- "@esbuild/win32-ia32": "0.25.1",
- "@esbuild/win32-x64": "0.25.1"
- }
- },
- "node_modules/tsx/node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/type-is": {
- "version": "1.6.18",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/typedarray": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
- "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/typescript": {
- "version": "5.8.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
- "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/typescript-eslint": {
- "version": "8.26.1",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.26.1.tgz",
- "integrity": "sha512-t/oIs9mYyrwZGRpDv3g+3K6nZ5uhKEMt2oNmAPwaY4/ye0+EH4nXIPYNtkYFS6QHm+1DFg34DbglYBz5P9Xysg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/eslint-plugin": "8.26.1",
- "@typescript-eslint/parser": "8.26.1",
- "@typescript-eslint/utils": "8.26.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/undici": {
- "version": "6.21.1",
- "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
- "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.17"
- }
- },
- "node_modules/undici-types": {
- "version": "6.19.8",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
- "license": "MIT"
- },
- "node_modules/universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4.0.0"
- }
- },
- "node_modules/unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/use-sync-external-store": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
- "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/uuid": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
- "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
- "dev": true,
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
- "node_modules/validate.io-array": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz",
- "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/validate.io-function": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz",
- "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==",
- "dev": true
- },
- "node_modules/vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/vue": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
- "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/compiler-dom": "3.5.13",
- "@vue/compiler-sfc": "3.5.13",
- "@vue/runtime-dom": "3.5.13",
- "@vue/server-renderer": "3.5.13",
- "@vue/shared": "3.5.13"
- },
- "peerDependencies": {
- "typescript": "*"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/web-streams-polyfill": {
- "version": "4.0.0-beta.3",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
- "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
- "license": "MIT",
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/whatwg-encoding": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
- "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "iconv-lite": "0.6.3"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/whatwg-mimetype": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
- "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/whatwg-url": {
- "version": "14.2.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
- "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
- "license": "MIT",
- "dependencies": {
- "tr46": "^5.1.0",
- "webidl-conversions": "^7.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/wrap-ansi/node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/wrap-ansi/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/wrap-ansi/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/wrap-ansi/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "license": "ISC"
- },
- "node_modules/ws": {
- "version": "8.18.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
- "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.4"
- }
- },
- "node_modules/yaml": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
- "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/zimmerframe": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
- "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/zod": {
- "version": "3.24.2",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
- "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- },
- "node_modules/zod-to-json-schema": {
- "version": "3.24.3",
- "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.3.tgz",
- "integrity": "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==",
- "license": "ISC",
- "peerDependencies": {
- "zod": "^3.24.1"
- }
- }
- }
-}
diff --git a/package.json b/package.json
index ad675a846..5757c9bf6 100644
--- a/package.json
+++ b/package.json
@@ -1,107 +1,39 @@
{
- "name": "@browserbasehq/stagehand",
- "version": "2.1.0",
- "description": "An AI web browsing framework focused on simplicity and extensibility.",
- "main": "./dist/index.js",
- "module": "./dist/index.js",
- "types": "./dist/index.d.ts",
+ "name": "stagehand-workspace",
+ "version": "0.0.0",
+ "private": true,
+ "description": "Stagehand monorepo workspace",
"scripts": {
- "2048": "npm run build && tsx examples/2048.ts",
- "popup": "npm run build && tsx examples/popup.ts",
- "cua": "npm run build && tsx examples/cua-example.ts",
- "operator": "npm run build && tsx examples/operator-example.ts",
- "example": "npm run build && tsx examples/example.ts",
- "langchain": "npm run build && tsx examples/langchain.ts",
- "debug-url": "npm run build && tsx examples/debugUrl.ts",
- "external-client": "npm run build && tsx examples/external_client.ts",
- "instructions": "npm run build && tsx examples/instructions.ts",
- "ai-sdk-client": "npm run build && tsx examples/ai_sdk_example.ts",
- "actionable_observe_example": "npm run build && tsx examples/actionable_observe_example.ts",
- "form-filling-sensible-cerebras": "npm run build && tsx examples/form_filling_sensible_cerebras.ts",
- "form-filling-sensible": "npm run build && tsx examples/form_filling_sensible.ts",
- "google-enter": "npm run build && tsx examples/google_enter.ts",
- "try-wordle": "npm run build && tsx examples/try_wordle.ts",
+ "build": "turbo run build",
+ "build:cli": "turbo run build:cli",
+ "lint": "turbo run lint",
"format": "prettier --write .",
- "prettier": "prettier --check .",
- "prettier:fix": "prettier --write .",
+ "prettier": "prettier --write .",
"eslint": "eslint .",
- "cache:clear": "rm -rf .cache",
- "evals": "npm run build && tsx evals/index.eval.ts",
- "e2e": "npm run build && cd evals/deterministic && npx playwright test --config=e2e.playwright.config.ts",
- "e2e:bb": "npm run build && cd evals/deterministic && npx playwright test --config=bb.playwright.config.ts",
- "e2e:local": "npm run build && cd evals/deterministic && npx playwright test --config=local.playwright.config.ts",
- "build-dom-scripts": "tsx lib/dom/genDomScripts.ts",
- "build-types": "tsc --emitDeclarationOnly --outDir dist",
- "build-js": "tsup lib/index.ts --dts",
- "build": "npm run lint && npm run build-dom-scripts && npm run build-js && npm run build-types",
- "lint": "npm run prettier && npm run eslint",
- "release": "npm run build && changeset publish",
- "release-canary": "npm run build && changeset version --snapshot && changeset publish --tag alpha"
+ "test": "turbo run test",
+ "e2e": "turbo run e2e",
+ "e2e:bb": "turbo run e2e:bb",
+ "e2e:local": "turbo run e2e:local",
+ "evals": "pnpm --filter @browserbasehq/stagehand-evals run evals",
+ "docs": "turbo run docs",
+ "dev": "turbo run dev",
+ "example": "pnpm --filter @browserbasehq/stagehand run example --",
+ "cache:clear": "turbo run build --force",
+ "prepare": "turbo run build",
+ "release": "turbo run build && changeset publish",
+ "release-canary": "turbo run build && changeset version --snapshot && changeset publish --tag alpha"
},
- "files": [
- "dist/**",
- "lib/**"
- ],
- "keywords": [],
- "author": "Browserbase",
- "license": "MIT",
"devDependencies": {
- "@ai-sdk/anthropic": "^1.2.6",
- "@ai-sdk/cerebras": "^0.2.6",
- "@ai-sdk/google": "^1.2.6",
- "@ai-sdk/groq": "^1.2.4",
- "@ai-sdk/openai": "^1.0.14",
- "@ai-sdk/togetherai": "^0.2.6",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.9",
"@eslint/js": "^9.16.0",
- "@langchain/core": "^0.3.40",
- "@langchain/openai": "^0.4.4",
- "@types/adm-zip": "^0.5.7",
- "@types/cheerio": "^0.22.35",
- "@types/express": "^4.17.21",
- "@types/node": "^20.11.30",
- "@types/ws": "^8.5.13",
- "adm-zip": "^0.5.16",
- "ai": "^4.3.0",
- "autoevals": "^0.0.64",
- "braintrust": "^0.0.171",
- "chalk": "^5.4.1",
- "cheerio": "^1.0.0",
- "chromium-bidi": "^0.10.0",
- "esbuild": "^0.21.4",
"eslint": "^9.16.0",
- "express": "^4.21.0",
"globals": "^15.13.0",
- "multer": "^1.4.5-lts.1",
"prettier": "^3.2.5",
- "string-comparison": "^1.3.0",
- "tsup": "^8.2.1",
- "tsx": "^4.10.5",
+ "turbo": "^2.5.8",
"typescript": "^5.2.2",
"typescript-eslint": "^8.17.0"
},
- "peerDependencies": {
- "@playwright/test": "^1.42.1",
- "deepmerge": "^4.3.1",
- "dotenv": "^16.4.5",
- "zod": "^3.23.8"
- },
- "dependencies": {
- "@anthropic-ai/sdk": "0.39.0",
- "@browserbasehq/sdk": "^2.4.0",
- "@google/genai": "^0.8.0",
- "openai": "^4.87.1",
- "pino": "^9.6.0",
- "pino-pretty": "^13.0.0",
- "ws": "^8.18.0",
- "zod-to-json-schema": "^3.23.5"
- },
- "directories": {
- "doc": "docs",
- "example": "examples",
- "lib": "lib"
- },
"repository": {
"type": "git",
"url": "git+https://github.com/browserbase/stagehand.git"
@@ -112,5 +44,6 @@
"homepage": "https://stagehand.dev",
"overrides": {
"whatwg-url": "^14.0.0"
- }
+ },
+ "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c"
}
diff --git a/packages/README.md b/packages/README.md
new file mode 100644
index 000000000..a92183d77
--- /dev/null
+++ b/packages/README.md
@@ -0,0 +1,7 @@
+# Stagehand Packages
+
+This directory contains the Stagehand monorepo packages:
+
+- **core** - The main Stagehand package
+- **evals** - Evals CLI
+- **docs** - [Docs](https://docs.stagehand.dev)
\ No newline at end of file
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
new file mode 100644
index 000000000..d9d9a7549
--- /dev/null
+++ b/packages/core/CHANGELOG.md
@@ -0,0 +1,173 @@
+# @browserbasehq/stagehand
+
+## 3.0.7
+
+### Patch Changes
+
+- [#1461](https://github.com/browserbase/stagehand/pull/1461) [`0f3991e`](https://github.com/browserbase/stagehand/commit/0f3991eedc0aaff72ef718dda3ddb0839cf4a464) Thanks [@tkattkat](https://github.com/tkattkat)! - Move hybrid mode out of experimental
+
+- [#1433](https://github.com/browserbase/stagehand/pull/1433) [`e0e22e0`](https://github.com/browserbase/stagehand/commit/e0e22e06bc752a8ffde30f3dbfa58d91e24e6c09) Thanks [@tkattkat](https://github.com/tkattkat)! - Put hybrid mode behind experimental
+
+- [#1456](https://github.com/browserbase/stagehand/pull/1456) [`f261051`](https://github.com/browserbase/stagehand/commit/f2610517d74774374de9ee93191e663439ef55e5) Thanks [@shrey150](https://github.com/shrey150)! - Invoke page.hover for agent move action
+
+- [#1473](https://github.com/browserbase/stagehand/pull/1473) [`e021674`](https://github.com/browserbase/stagehand/commit/e021674f9641c1c5f9d0c1817c3fdf599eea124d) Thanks [@shrey150](https://github.com/shrey150)! - Add safety confirmation support for OpenAI + Google CUA
+
+- [#1399](https://github.com/browserbase/stagehand/pull/1399) [`6a5496f`](https://github.com/browserbase/stagehand/commit/6a5496f17dbb716be1ee1aaa4e5ba9d8c723b30b) Thanks [@tkattkat](https://github.com/tkattkat)! - Ensure cua agent is killed when stagehand.close is called
+
+- [#1436](https://github.com/browserbase/stagehand/pull/1436) [`fea1700`](https://github.com/browserbase/stagehand/commit/fea1700552af3319052f463685752501c8e71de3) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix auto-load key for act/extract/observe parametrized models on api
+
+- [#1439](https://github.com/browserbase/stagehand/pull/1439) [`5b288d9`](https://github.com/browserbase/stagehand/commit/5b288d9ac37406ff22460ac8050bea26b87a378e) Thanks [@tkattkat](https://github.com/tkattkat)! - Remove base64 from agent actions array ( still present in messages object )
+
+- [#1408](https://github.com/browserbase/stagehand/pull/1408) [`e822f5a`](https://github.com/browserbase/stagehand/commit/e822f5a8898df9eb48ca32c321025f0c74b638f0) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - allow for act() cache hit when variable values change
+
+- [#1472](https://github.com/browserbase/stagehand/pull/1472) [`638efc7`](https://github.com/browserbase/stagehand/commit/638efc7fea401bc43dd05dceedf4c13a3495a728) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: agent cache not refreshed on action failure
+
+- [#1424](https://github.com/browserbase/stagehand/pull/1424) [`a890f16`](https://github.com/browserbase/stagehand/commit/a890f16fa3a752f308f858e5ab9c9a0faf6b3b34) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: "Error: -32000 Failed to convert response to JSON: CBOR: stack limit exceeded"
+
+- [#1418](https://github.com/browserbase/stagehand/pull/1418) [`934f492`](https://github.com/browserbase/stagehand/commit/934f492ec587bef81f0ce75b45a35b44ab545712) Thanks [@miguelg719](https://github.com/miguelg719)! - Cleanup handlers and bus listeners on close
+
+- [#1430](https://github.com/browserbase/stagehand/pull/1430) [`bd2db92`](https://github.com/browserbase/stagehand/commit/bd2db925f66a826d61d58be1611d55646cbdb560) Thanks [@shrey150](https://github.com/shrey150)! - Fix CUA model coordinate translation
+
+- [#1465](https://github.com/browserbase/stagehand/pull/1465) [`51e0170`](https://github.com/browserbase/stagehand/commit/51e01709ce1c947c1947b4e2cb0b1f4f97b77182) Thanks [@miguelg719](https://github.com/miguelg719)! - Add media resolution high provider option to gemini 3 hybrid agent
+
+- [#1431](https://github.com/browserbase/stagehand/pull/1431) [`05f5580`](https://github.com/browserbase/stagehand/commit/05f5580937c3c157550e3c25ae6671f44f562211) Thanks [@tkattkat](https://github.com/tkattkat)! - Update the cache handling for agent
+
+- [#1432](https://github.com/browserbase/stagehand/pull/1432) [`f56a9c2`](https://github.com/browserbase/stagehand/commit/f56a9c296d4ddce25a405358c66837f8ce4d679f) Thanks [@tkattkat](https://github.com/tkattkat)! - Deprecate cua: true in favor of mode: "cua"
+
+- [#1406](https://github.com/browserbase/stagehand/pull/1406) [`b40ae11`](https://github.com/browserbase/stagehand/commit/b40ae11391af49c3581fce27faa1b7483fc4a169) Thanks [@tkattkat](https://github.com/tkattkat)! - Add support for hovering with coordinates ( page.hover )
+
+- [#1407](https://github.com/browserbase/stagehand/pull/1407) [`0d2b398`](https://github.com/browserbase/stagehand/commit/0d2b398cd40b32a9ecaf28ede70853036b7c91bd) Thanks [@tkattkat](https://github.com/tkattkat)! - Clean up page methods
+
+- [#1412](https://github.com/browserbase/stagehand/pull/1412) [`cd01f29`](https://github.com/browserbase/stagehand/commit/cd01f290578eac703521f801ba3712f5332918f3) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: load GOOGLE_API_KEY from .env
+
+- [#1462](https://github.com/browserbase/stagehand/pull/1462) [`a734fca`](https://github.com/browserbase/stagehand/commit/a734fca0b4573753767d3ebc48ec414baf4f23e1) Thanks [@shrey150](https://github.com/shrey150)! - fix: correctly pass userDataDir to chrome launcher
+
+- [#1466](https://github.com/browserbase/stagehand/pull/1466) [`b342acf`](https://github.com/browserbase/stagehand/commit/b342acfaae058127fb57664644c5fd965db02bf2) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - move playwright to optional dependencies
+
+- [#1440](https://github.com/browserbase/stagehand/pull/1440) [`2987cd1`](https://github.com/browserbase/stagehand/commit/2987cd1e5ffabefa9411936609635d4a638faed5) Thanks [@tkattkat](https://github.com/tkattkat)! - [Feature] support excluding tools from agent
+
+- [#1455](https://github.com/browserbase/stagehand/pull/1455) [`dfab1d5`](https://github.com/browserbase/stagehand/commit/dfab1d566299c8c5a63f20565a6da07dc8f61ccd) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - update aisdk client to better enforce structured output with deepseek models
+
+- [#1428](https://github.com/browserbase/stagehand/pull/1428) [`4d71162`](https://github.com/browserbase/stagehand/commit/4d71162beb119635b69b17637564a2bbd0e373e7) Thanks [@tkattkat](https://github.com/tkattkat)! - Add "hybrid" mode to stagehand agent
+
+## 3.0.6
+
+### Patch Changes
+
+- [#1388](https://github.com/browserbase/stagehand/pull/1388) [`605ed6b`](https://github.com/browserbase/stagehand/commit/605ed6b81a3ff8f25d4022f1e5fce6b42aecfc19) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix multiple click event dispatches on CDP and Anthropic CUA handling (double clicks)
+
+- [#1400](https://github.com/browserbase/stagehand/pull/1400) [`34e7e5b`](https://github.com/browserbase/stagehand/commit/34e7e5b292f5e6af6efc0da60118663310c5f718) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - don't write base64 encoded screenshots to disk when caching agent actions
+
+- [#1345](https://github.com/browserbase/stagehand/pull/1345) [`943d2d7`](https://github.com/browserbase/stagehand/commit/943d2d79d0f289ac41c9164578f2f1dd876058f2) Thanks [@tkattkat](https://github.com/tkattkat)! - Add support for aborting / stopping an agent run & continuing an agent run using messages from prior runs
+
+- [#1334](https://github.com/browserbase/stagehand/pull/1334) [`0e95cd2`](https://github.com/browserbase/stagehand/commit/0e95cd2f67672f64f0017024fd47d8b3aef59a95) Thanks [@tkattkat](https://github.com/tkattkat)! - Add support for google vertex provider
+
+- [#1410](https://github.com/browserbase/stagehand/pull/1410) [`d4237e4`](https://github.com/browserbase/stagehand/commit/d4237e40951ecd10abfdbe766672d498f8806484) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: include extract in stagehand.history()
+
+- [#1315](https://github.com/browserbase/stagehand/pull/1315) [`86975e7`](https://github.com/browserbase/stagehand/commit/86975e795db7505804949a267b20509bd16b5256) Thanks [@tkattkat](https://github.com/tkattkat)! - Add streaming support to agent through stream:true in the agent config
+
+- [#1304](https://github.com/browserbase/stagehand/pull/1304) [`d5e119b`](https://github.com/browserbase/stagehand/commit/d5e119be5eec84915a79f8d611b6ba0546f48c99) Thanks [@miguelg719](https://github.com/miguelg719)! - Add support for Microsoft's Fara-7B
+
+- [#1346](https://github.com/browserbase/stagehand/pull/1346) [`4e051b2`](https://github.com/browserbase/stagehand/commit/4e051b23add7ae276b0dbead38b4587838cfc1c1) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: don't attach to targets twice
+
+- [#1327](https://github.com/browserbase/stagehand/pull/1327) [`6b5a3c9`](https://github.com/browserbase/stagehand/commit/6b5a3c9035654caaed2da375085b465edda97de4) Thanks [@miguelg719](https://github.com/miguelg719)! - Informed error parsing from api
+
+- [#1335](https://github.com/browserbase/stagehand/pull/1335) [`bb85ad9`](https://github.com/browserbase/stagehand/commit/bb85ad912738623a7a866f0cb6e8d5807c6c2738) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add support for page.addInitScript()
+
+- [#1331](https://github.com/browserbase/stagehand/pull/1331) [`88d28cc`](https://github.com/browserbase/stagehand/commit/88d28cc6f31058d1cf6ec6dc948a4ae77a926b3c) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: page.evaluate() now works with scripts injected via context.addInitScript()
+
+- [#1316](https://github.com/browserbase/stagehand/pull/1316) [`45bcef0`](https://github.com/browserbase/stagehand/commit/45bcef0e5788b083f9e38dfd7c3bc63afcd4b6dd) Thanks [@tkattkat](https://github.com/tkattkat)! - Add support for callbacks in stagehand agent
+
+- [#1374](https://github.com/browserbase/stagehand/pull/1374) [`6aa9d45`](https://github.com/browserbase/stagehand/commit/6aa9d455aa5836ec2ee8ab2e8b9df3fb218e5381) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix key action mapping in Anthropic CUA
+
+- [#1330](https://github.com/browserbase/stagehand/pull/1330) [`d382084`](https://github.com/browserbase/stagehand/commit/d382084745fff98c3e71413371466394a2625429) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: make act, extract, and observe respect user defined timeout param
+
+- [#1336](https://github.com/browserbase/stagehand/pull/1336) [`1df08cc`](https://github.com/browserbase/stagehand/commit/1df08ccb0a2cf73b5c37a91c129721114ff6371c) Thanks [@tkattkat](https://github.com/tkattkat)! - Patch agent on api
+
+- [#1358](https://github.com/browserbase/stagehand/pull/1358) [`2b56600`](https://github.com/browserbase/stagehand/commit/2b566009606fcbba987260f21b075b318690ce99) Thanks [@tkattkat](https://github.com/tkattkat)! - Add support for 4.5 opus in cua agent
+
+## 3.0.4
+
+### Patch Changes
+
+- [#1281](https://github.com/browserbase/stagehand/pull/1281) [`fa18cfd`](https://github.com/browserbase/stagehand/commit/fa18cfdc45f28e35e6566587b54612396e6ece45) Thanks [@monadoid](https://github.com/monadoid)! - Add Browserbase session URL and debug URL accessors
+
+- [#1264](https://github.com/browserbase/stagehand/pull/1264) [`767d168`](https://github.com/browserbase/stagehand/commit/767d1686285cf9c57675595f553f8a891f13c63b) Thanks [@Kylejeong2](https://github.com/Kylejeong2)! - feat: adding gpt 5.1 to stagehand
+
+- [#1282](https://github.com/browserbase/stagehand/pull/1282) [`f27a99c`](https://github.com/browserbase/stagehand/commit/f27a99c11b020b33736fe67af8f7f0e663c6f45f) Thanks [@tkattkat](https://github.com/tkattkat)! - Add support for zod 4, while maintaining backwards compatibility for zod 3
+
+- [#1295](https://github.com/browserbase/stagehand/pull/1295) [`91a1ca0`](https://github.com/browserbase/stagehand/commit/91a1ca07d9178c46269bfb951abb20a215eb7c29) Thanks [@tkattkat](https://github.com/tkattkat)! - Patch zod handling of non objects in extract
+
+- [#1298](https://github.com/browserbase/stagehand/pull/1298) [`1dd7d43`](https://github.com/browserbase/stagehand/commit/1dd7d4330de9022dc6cd45a8b5c86cb9e1b575ec) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - log Browserbase session status when websocket is closed due to session timeout
+
+- [#1284](https://github.com/browserbase/stagehand/pull/1284) [`c0f3b98`](https://github.com/browserbase/stagehand/commit/c0f3b98277c15c77b2b4c3f55503e61ef3d27cf3) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: waitForDomNetworkQuiet() causing `act()` to hang indefinitely
+
+- [#1246](https://github.com/browserbase/stagehand/pull/1246) [`44bb4f5`](https://github.com/browserbase/stagehand/commit/44bb4f51dcccbdca8df07e4d7f8d28a7e6e793ec) Thanks [@filip-michalsky](https://github.com/filip-michalsky)! - make ci faster
+
+- [#1300](https://github.com/browserbase/stagehand/pull/1300) [`2b70347`](https://github.com/browserbase/stagehand/commit/2b7034771bc6d6b1fabb13deaa56c299881b3728) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add support for context.addInitScript()
+
+## 3.0.3
+
+### Patch Changes
+
+- [#1273](https://github.com/browserbase/stagehand/pull/1273) [`ab51232`](https://github.com/browserbase/stagehand/commit/ab51232db428be048957c0f5d67f2176eb7a5194) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: trigger shadow root rerender in OOPIFs by cloning & replacing instead of reloading
+
+- [#1268](https://github.com/browserbase/stagehand/pull/1268) [`c76ade0`](https://github.com/browserbase/stagehand/commit/c76ade009ef81208accae6475ec4707d3906e566) Thanks [@tkattkat](https://github.com/tkattkat)! - Expose reasoning, and cached input tokens in stagehand metrics
+
+- [#1267](https://github.com/browserbase/stagehand/pull/1267) [`ffb5e5d`](https://github.com/browserbase/stagehand/commit/ffb5e5d2ab49adcb2efdfc9e5c76e8c96268b5b3) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: file uploads failing on Browserbase
+
+- [#1269](https://github.com/browserbase/stagehand/pull/1269) [`772e735`](https://github.com/browserbase/stagehand/commit/772e73543e45106d7fa0fafd95ade46ae11023bc) Thanks [@tkattkat](https://github.com/tkattkat)! - Add example using playwright screen recording
+
+## 3.0.2
+
+### Patch Changes
+
+- [#1245](https://github.com/browserbase/stagehand/pull/1245) [`a224b33`](https://github.com/browserbase/stagehand/commit/a224b3371b6c1470baf342742fb745c7192b52c6) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - allow act() to call hover()
+
+- [#1234](https://github.com/browserbase/stagehand/pull/1234) [`6fc9de2`](https://github.com/browserbase/stagehand/commit/6fc9de2a1079e4f2fb0b1633d8df0bb7a9f7f89f) Thanks [@miguelg719](https://github.com/miguelg719)! - Add a page.sendCDP method
+
+- [#1233](https://github.com/browserbase/stagehand/pull/1233) [`4935be7`](https://github.com/browserbase/stagehand/commit/4935be788b3431527f3d110864c0fd7060cfaf7c) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - extend page.screenshot() options to mirror playwright
+
+- [#1232](https://github.com/browserbase/stagehand/pull/1232) [`bdd76fc`](https://github.com/browserbase/stagehand/commit/bdd76fcd1e48079fc5ab8cf040ebb5997dfc6c99) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - export Page type
+
+- [#1229](https://github.com/browserbase/stagehand/pull/1229) [`7ea18a4`](https://github.com/browserbase/stagehand/commit/7ea18a420fc033d1b72556db83a1f41735e5a022) Thanks [@tkattkat](https://github.com/tkattkat)! - Adjust extract tool + expose extract response in agent result
+
+- [#1239](https://github.com/browserbase/stagehand/pull/1239) [`d4de014`](https://github.com/browserbase/stagehand/commit/d4de014235a18f9e1089240bc72e28cbfe77ca1c) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix stagehand.metrics on api mode
+
+- [#1241](https://github.com/browserbase/stagehand/pull/1241) [`2d1b573`](https://github.com/browserbase/stagehand/commit/2d1b5732dc441a3331f5743cdfed3e1037d8b3b5) Thanks [@miguelg719](https://github.com/miguelg719)! - Return response on page.goto api mode
+
+- [#1253](https://github.com/browserbase/stagehand/pull/1253) [`5556041`](https://github.com/browserbase/stagehand/commit/5556041e2deaed5012363303fd7a8ac00e3242cd) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix missing page issue when connecting to existing browser
+
+- [#1235](https://github.com/browserbase/stagehand/pull/1235) [`7e4b43e`](https://github.com/browserbase/stagehand/commit/7e4b43ed46fbdd2074827e87d9a245e2dc96456b) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - make page.goto() return a Response object
+
+- [#1254](https://github.com/browserbase/stagehand/pull/1254) [`7e72adf`](https://github.com/browserbase/stagehand/commit/7e72adfd7e4af5ec49ac2f552e7f1f57c1acc554) Thanks [@sameelarif](https://github.com/sameelarif)! - Added custom error types to allow for a smoother debugging experience.
+
+- [#1227](https://github.com/browserbase/stagehand/pull/1227) [`9bf09d0`](https://github.com/browserbase/stagehand/commit/9bf09d041111870d71cb9ffcb3ac5fa2c4b1399d) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix readme's media links and add instructions for installing from a branch
+
+- [#1257](https://github.com/browserbase/stagehand/pull/1257) [`92d32ea`](https://github.com/browserbase/stagehand/commit/92d32eafe91a4241615cc65501b8461c6074a02b) Thanks [@tkattkat](https://github.com/tkattkat)! - Add support for a custom baseUrl with google cua client
+
+- [#1230](https://github.com/browserbase/stagehand/pull/1230) [`ebcf3a1`](https://github.com/browserbase/stagehand/commit/ebcf3a1ffa859374d71de4931c6a9b982a565e46) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add stagehand.browserbaseSessionID getter
+
+- [#1262](https://github.com/browserbase/stagehand/pull/1262) [`c29a4f2`](https://github.com/browserbase/stagehand/commit/c29a4f2eca91ae2902ed9d48b2385b4436f7b664) Thanks [@miguelg719](https://github.com/miguelg719)! - Remove error throwing when api and experimental are both set
+
+- [#1223](https://github.com/browserbase/stagehand/pull/1223) [`6d21efa`](https://github.com/browserbase/stagehand/commit/6d21efa8b30317aa3ce3e37ac6c2222af3b967b5) Thanks [@miguelg719](https://github.com/miguelg719)! - Disable api mode when using custom LLM clients
+
+- [#1228](https://github.com/browserbase/stagehand/pull/1228) [`525ef0c`](https://github.com/browserbase/stagehand/commit/525ef0c1243aaf3452ee7e4ea81b4208f4c2efd1) Thanks [@Kylejeong2](https://github.com/Kylejeong2)! - update slack link in docs
+
+- [#1226](https://github.com/browserbase/stagehand/pull/1226) [`9ddb872`](https://github.com/browserbase/stagehand/commit/9ddb872e350358214e12a91cf6a614fd2ec1f74c) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add support for page.on('console') events
+
+## 3.0.1
+
+### Patch Changes
+
+- [#1207](https://github.com/browserbase/stagehand/pull/1207) [`55da8c6`](https://github.com/browserbase/stagehand/commit/55da8c6e9575cbad3246c55b17650cf6b293ddbe) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix broken links to quickstart docs
+
+- [#1200](https://github.com/browserbase/stagehand/pull/1200) [`0a5ee63`](https://github.com/browserbase/stagehand/commit/0a5ee638bde051d109eb2266e665934a12f3dc31) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - log info when scope narrowing selector fails
+
+- [#1205](https://github.com/browserbase/stagehand/pull/1205) [`ee76881`](https://github.com/browserbase/stagehand/commit/ee7688156cb67a9f0f90dfe0dbab77423693a332) Thanks [@miguelg719](https://github.com/miguelg719)! - Update README.md, add Changelog for v3
+
+- [#1209](https://github.com/browserbase/stagehand/pull/1209) [`9e95add`](https://github.com/browserbase/stagehand/commit/9e95add37eb30db4f85e73df7760c7e63fb4131e) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix circular import in exported aisdk example client
+
+- [#1211](https://github.com/browserbase/stagehand/pull/1211) [`98e212b`](https://github.com/browserbase/stagehand/commit/98e212b27887241879608c6c1b6c2524477a40d7) Thanks [@miguelg719](https://github.com/miguelg719)! - Add an example for passing custom tools to agent
+
+- [#1206](https://github.com/browserbase/stagehand/pull/1206) [`d5ecbfc`](https://github.com/browserbase/stagehand/commit/d5ecbfc8e419a59b91c2115fd7f984378381d3d0) Thanks [@miguelg719](https://github.com/miguelg719)! - Export example AISdkClient properly from the stagehand package
diff --git a/packages/core/README.md b/packages/core/README.md
new file mode 100644
index 000000000..7df1261d6
--- /dev/null
+++ b/packages/core/README.md
@@ -0,0 +1,164 @@
+
+
+ The AI Browser Automation Framework
+ Read the Docs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+If you're looking for the Python implementation, you can find it
+ here
+
+
+
+
+## What is Stagehand?
+
+Stagehand is a browser automation framework used to control web browsers with natural language and code. By combining the power of AI with the precision of code, Stagehand makes web automation flexible, maintainable, and actually reliable.
+
+## Why Stagehand?
+
+Most existing browser automation tools either require you to write low-level code in a framework like Selenium, Playwright, or Puppeteer, or use high-level agents that can be unpredictable in production. By letting developers choose what to write in code vs. natural language (and bridging the gap between the two) Stagehand is the natural choice for browser automations in production.
+
+1. **Choose when to write code vs. natural language**: use AI when you want to navigate unfamiliar pages, and use code when you know exactly what you want to do.
+
+2. **Go from AI-driven to repeatable workflows**: Stagehand lets you preview AI actions before running them, and also helps you easily cache repeatable actions to save time and tokens.
+
+3. **Write once, run forever**: Stagehand's auto-caching combined with self-healing remembers previous actions, runs without LLM inference, and knows when to involve AI whenever the website changes and your automation breaks.
+
+## Getting Started
+
+Start with Stagehand with one line of code, or check out our [Quickstart Guide](https://docs.stagehand.dev/v3/first-steps/quickstart) for more information:
+
+```bash
+npx create-browser-app
+```
+
+## Example
+
+Here's how to build a sample browser automation with Stagehand:
+
+```typescript
+// Stagehand's CDP engine provides an optimized, low level interface to the browser built for automation
+const page = stagehand.context.pages()[0];
+await page.goto("https://github.com/browserbase");
+
+// Use act() to execute individual actions
+await stagehand.act("click on the stagehand repo");
+
+// Use agent() for multi-step tasks
+const agent = stagehand.agent();
+await agent.execute("Get to the latest PR");
+
+// Use extract() to get structured data from the page
+const { author, title } = await stagehand.extract(
+ "extract the author and title of the PR",
+ z.object({
+ author: z.string().describe("The username of the PR author"),
+ title: z.string().describe("The title of the PR"),
+ }),
+);
+```
+
+## Documentation
+
+Visit [docs.stagehand.dev](https://docs.stagehand.dev) to view the full documentation.
+
+### Build and Run from Source
+
+```bash
+git clone https://github.com/browserbase/stagehand.git
+cd stagehand
+pnpm install
+pnpm run build
+pnpm run example # run the blank script at ./examples/example.ts
+```
+
+Stagehand is best when you have an API key for an LLM provider and Browserbase credentials. To add these to your project, run:
+
+```bash
+cp .env.example .env
+nano .env # Edit the .env file to add API keys
+```
+
+### Installing from a branch
+
+You can install and build Stagehand directly from a github branch using [gitpkg](https://github.com/EqualMa/gitpkg)
+
+In your project's `package.json` set:
+
+```json
+"@browserbasehq/stagehand": "https://gitpkg.now.sh/browserbase/stagehand/packages/core?",
+```
+
+## Contributing
+
+> [!NOTE]
+> We highly value contributions to Stagehand! For questions or support, please join our [Discord community](https://stagehand.dev/discord).
+
+At a high level, we're focused on improving reliability, extensibility, speed, and cost in that order of priority. If you're interested in contributing, **bug fixes and small improvements are the best way to get started**. For more involved features, we strongly recommend reaching out to [Miguel Gonzalez](https://x.com/miguel_gonzf) or [Paul Klein](https://x.com/pk_iv) in our [Discord community](https://stagehand.dev/discord) before starting to ensure that your contribution aligns with our goals.
+
+
+
+## Acknowledgements
+
+We'd like to thank the following people for their major contributions to Stagehand:
+
+- [Paul Klein](https://github.com/pkiv)
+- [Sean McGuire](https://github.com/seanmcguire12)
+- [Miguel Gonzalez](https://github.com/miguelg719)
+- [Sameel Arif](https://github.com/sameelarif)
+- [Thomas Katwan](https://github.com/tkattkat)
+- [Filip Michalsky](https://github.com/filip-michalsky)
+- [Anirudh Kamath](https://github.com/kamath)
+- [Jeremy Press](https://x.com/jeremypress)
+- [Navid Pour](https://github.com/navidpour)
+
+## License
+
+Licensed under the MIT License.
+
+Copyright 2025 Browserbase, Inc.
diff --git a/examples/2048.ts b/packages/core/examples/2048.ts
similarity index 79%
rename from examples/2048.ts
rename to packages/core/examples/2048.ts
index 9c651198a..5cb76b3d9 100644
--- a/examples/2048.ts
+++ b/packages/core/examples/2048.ts
@@ -1,4 +1,4 @@
-import { Stagehand } from "@/dist";
+import { Stagehand } from "../lib/v3";
import { z } from "zod";
async function example() {
@@ -6,32 +6,31 @@ async function example() {
const stagehand = new Stagehand({
env: "LOCAL",
verbose: 1,
- domSettleTimeoutMs: 100,
});
+
+ console.log("🌟 Initializing Stagehand...");
+ await stagehand.init();
+ const page = stagehand.context.pages()[0];
try {
- console.log("🌟 Initializing Stagehand...");
- await stagehand.init();
console.log("🌐 Navigating to 2048...");
- await stagehand.page.goto("https://ovolve.github.io/2048-AI/");
- console.log("⌛ Waiting for game to initialize...");
- await stagehand.page.waitForSelector(".grid-container", { timeout: 10000 });
+ await page.goto("https://ovolve.github.io/2048-AI/");
// Main game loop
while (true) {
console.log("🔄 Game loop iteration...");
// Add a small delay for UI updates
await new Promise((resolve) => setTimeout(resolve, 300));
// Get current game state
- const gameState = await stagehand.page.extract({
- instruction: `Extract the current game state:
+ const gameState = await stagehand.extract(
+ `Extract the current game state:
1. Score from the score counter
2. All tile values in the 4x4 grid (empty spaces as 0)
3. Highest tile value present`,
- schema: z.object({
+ z.object({
score: z.number(),
highestTile: z.number(),
grid: z.array(z.array(z.number())),
}),
- });
+ );
const transposedGrid = gameState.grid[0].map((_, colIndex) =>
gameState.grid.map((row) => row[colIndex]),
);
@@ -44,8 +43,8 @@ async function example() {
grid: grid,
});
// Analyze board and decide next move
- const analysis = await stagehand.page.extract({
- instruction: `Based on the current game state:
+ const analysis = await stagehand.extract(
+ `Based on the current game state:
- Score: ${gameState.score}
- Highest tile: ${gameState.highestTile}
- Grid: This is a 4x4 matrix ordered by row (top to bottom) and column (left to right). The rows are stacked vertically, and tiles can move vertically between rows or horizontally between columns:\n${grid
@@ -62,12 +61,12 @@ async function example() {
5. Making a move will move all tiles in that direction until they hit a tile of a different value or the edge of the board
6. Tiles cannot move past the edge of the board
7. Each move must move at least one tile`,
- schema: z.object({
+ z.object({
move: z.enum(["up", "down", "left", "right"]),
confidence: z.number(),
reasoning: z.string(),
}),
- });
+ );
console.log("Move Analysis:", analysis);
const moveKey = {
up: "ArrowUp",
@@ -75,12 +74,12 @@ async function example() {
left: "ArrowLeft",
right: "ArrowRight",
}[analysis.move];
- await stagehand.page.keyboard.press(moveKey);
+ await page.keyPress(moveKey);
console.log("🎯 Executed move:", analysis.move);
}
} catch (error) {
console.error("❌ Error in game loop:", error);
- const isGameOver = await stagehand.page.evaluate(() => {
+ const isGameOver = await page.evaluate(() => {
return document.querySelector(".game-over") !== null;
});
if (isGameOver) {
diff --git a/packages/core/examples/CHANGELOG.md b/packages/core/examples/CHANGELOG.md
new file mode 100644
index 000000000..4db2cda02
--- /dev/null
+++ b/packages/core/examples/CHANGELOG.md
@@ -0,0 +1,64 @@
+# @browserbasehq/stagehand-examples
+
+## 1.0.9
+
+### Patch Changes
+
+- Updated dependencies [[`09b5e1e`](https://github.com/browserbase/stagehand/commit/09b5e1e9c23c845903686db6665cc968ac34efbb), [`e3734b9`](https://github.com/browserbase/stagehand/commit/e3734b9c98352d5f0a4eca49791b0bbf2130ab41), [`8244ab2`](https://github.com/browserbase/stagehand/commit/8244ab247cd679962685ae2f7c54e874ce1fa614), [`be85b19`](https://github.com/browserbase/stagehand/commit/be85b19679a826f19702e00f0aae72fce1118ec8), [`88d1565`](https://github.com/browserbase/stagehand/commit/88d1565c65bb65a104fea2d5f5e862bbbda69677), [`ab5d6ed`](https://github.com/browserbase/stagehand/commit/ab5d6ede19aabc059badc4247f1cb2c6c9e71bae)]:
+ - @browserbasehq/stagehand@2.5.0
+
+## 1.0.8
+
+### Patch Changes
+
+- Updated dependencies [[`9e8c173`](https://github.com/browserbase/stagehand/commit/9e8c17374fdc8fbe7f26e6cf802c36bd14f11039)]:
+ - @browserbasehq/stagehand@2.4.4
+
+## 1.0.7
+
+### Patch Changes
+
+- Updated dependencies [[`f45afdc`](https://github.com/browserbase/stagehand/commit/f45afdccc8680650755fee66ffbeac32b41e075d), [`261bba4`](https://github.com/browserbase/stagehand/commit/261bba43fa79ac3af95328e673ef3e9fced3279b), [`8de7bd8`](https://github.com/browserbase/stagehand/commit/8de7bd8635c2051cd8025e365c6c8aa83d81c7e7), [`3d80421`](https://github.com/browserbase/stagehand/commit/3d804210a106a6828c7fa50f8b765b10afd4cc6a), [`0ead63d`](https://github.com/browserbase/stagehand/commit/0ead63d6526f6c286362b74b6407c8bebc900e69), [`8422828`](https://github.com/browserbase/stagehand/commit/8422828c4cd5fd5ebcf348cfbdb40c768bb76dd9), [`b769206`](https://github.com/browserbase/stagehand/commit/b7692060f98a2f49aeeefb90d8789ed034b08ec2), [`72d2683`](https://github.com/browserbase/stagehand/commit/72d2683202af7e578d98367893964b33e0828de5)]:
+ - @browserbasehq/stagehand@2.4.3
+
+## 1.0.6
+
+### Patch Changes
+
+- Updated dependencies [[`6b4e6e3`](https://github.com/browserbase/stagehand/commit/6b4e6e3f31d5496cf15728e9018eddeb04839542), [`e77d018`](https://github.com/browserbase/stagehand/commit/e77d0188683ebf596dfb78dfafbbca1dc32993f0), [`c20adb9`](https://github.com/browserbase/stagehand/commit/c20adb95539fed8c56a4aa413262a9c65a8e6474), [`b86df93`](https://github.com/browserbase/stagehand/commit/b86df93b9136aae96292121a29c25f3d74d84bf7), [`023c2c2`](https://github.com/browserbase/stagehand/commit/023c2c273b46d3792d7e5d3c902089487b16b531), [`8c28647`](https://github.com/browserbase/stagehand/commit/8c2864755ecd05c8f7de235d4198deec0dd5f78e), [`87e09c6`](https://github.com/browserbase/stagehand/commit/87e09c618940f364ec8af00455a19a17ec63cbd3), [`a611115`](https://github.com/browserbase/stagehand/commit/a61111525d70b450bdfc43f112380f44899c9e97), [`69913fe`](https://github.com/browserbase/stagehand/commit/69913fe1dfb8201ae2aeffa5f049fb46ab02cbc2), [`b1b83a1`](https://github.com/browserbase/stagehand/commit/b1b83a1d334fe76e5f5f9dd32dc92c16b7d40ce6), [`be8497c`](https://github.com/browserbase/stagehand/commit/be8497cb6b142cc893cea9692b8c47bd19514c60), [`98704c9`](https://github.com/browserbase/stagehand/commit/98704c9ed225ca25bbde4bb3dc286936e9c54471), [`04978bd`](https://github.com/browserbase/stagehand/commit/04978bdd30d2edcbc69eb9fd91358a16975ea2eb)]:
+ - @browserbasehq/stagehand@2.4.2
+
+## 1.0.5
+
+### Patch Changes
+
+- Updated dependencies [[`8a43c5a`](https://github.com/browserbase/stagehand/commit/8a43c5a86d4da40cfaedd9cf2e42186928bdf946), [`890ffcc`](https://github.com/browserbase/stagehand/commit/890ffccac5e0a60ade64a46eb550c981ffb3e84a), [`64c1072`](https://github.com/browserbase/stagehand/commit/64c10727bda50470483a3eb175c02842db0923a1), [`b077d3f`](https://github.com/browserbase/stagehand/commit/b077d3f48a97f47a71ccc79ae39b41e7f07f9c04), [`8bcb5d7`](https://github.com/browserbase/stagehand/commit/8bcb5d77debf6bf7601fd5c090efd7fde75c5d5e), [`7bf10c5`](https://github.com/browserbase/stagehand/commit/7bf10c55b267078fe847c1d7f7a60d604f9c7c94)]:
+ - @browserbasehq/stagehand@2.4.1
+
+## 1.0.4
+
+### Patch Changes
+
+- Updated dependencies [[`124e0d3`](https://github.com/browserbase/stagehand/commit/124e0d3bb54ddb6738ede6d7aa99a945ef1cacd1), [`6a18c1e`](https://github.com/browserbase/stagehand/commit/6a18c1ee1e46d55c6e90c4d5572e17ed8daa140c), [`1660751`](https://github.com/browserbase/stagehand/commit/1660751cd14cb5b27d44f8167216afb8d1c3c45c), [`cadac9d`](https://github.com/browserbase/stagehand/commit/cadac9da09123d12e5d496a0e8b12660964c1b33), [`759da55`](https://github.com/browserbase/stagehand/commit/759da55775eb2df81d56ae18c0f386fd9b02a9f0), [`a175a51`](https://github.com/browserbase/stagehand/commit/a175a519b8c14300db6f1ed30709e113d18e99db), [`8527a80`](https://github.com/browserbase/stagehand/commit/8527a80522c3eedb9516a6caa1a0e4e4be981a3d), [`55fca2f`](https://github.com/browserbase/stagehand/commit/55fca2f7da63cc0ef6e27b45a33f63c666cdce7e)]:
+ - @browserbasehq/stagehand@2.4.0
+
+## 1.0.3
+
+### Patch Changes
+
+- Updated dependencies [[`12a99b3`](https://github.com/browserbase/stagehand/commit/12a99b398d8a4c3eea3ca69a3cf793faaaf4aea3), [`2451797`](https://github.com/browserbase/stagehand/commit/2451797f64c0efa4a72fd70265110003c8d0a6cd), [`1d631a5`](https://github.com/browserbase/stagehand/commit/1d631a57a197390f672b718ae5199991ab27cfb1), [`9c398bb`](https://github.com/browserbase/stagehand/commit/9c398bb9ec2d10bdb53ad5aa7e3b58cce24fdb2b), [`c19ad7f`](https://github.com/browserbase/stagehand/commit/c19ad7f1e082e91fdeaa9c2ef63767a5a2b3a195)]:
+ - @browserbasehq/stagehand@2.3.1
+
+## 1.0.2
+
+### Patch Changes
+
+- Updated dependencies [[`5680d25`](https://github.com/browserbase/stagehand/commit/5680d2509352c383ad502c9f4fabde01fa638833), [`4de92a8`](https://github.com/browserbase/stagehand/commit/4de92a8af461fc95063faf39feee1d49259f58ba), [`6ef6073`](https://github.com/browserbase/stagehand/commit/6ef60730cab0ad9025f44b6eeb2c83751d1dcd35)]:
+ - @browserbasehq/stagehand@2.3.0
+
+## 1.0.1
+
+### Patch Changes
+
+- Updated dependencies [[`be8652e`](https://github.com/browserbase/stagehand/commit/be8652e770b57fdb3299fa0b2efa4eb0e816434e), [`6b413b7`](https://github.com/browserbase/stagehand/commit/6b413b7ad00b13ca0bd53ee2e7393023821408b6), [`7eafbd9`](https://github.com/browserbase/stagehand/commit/7eafbd9b1a73b37effa444929767df7c592caf02), [`1b50aa6`](https://github.com/browserbase/stagehand/commit/1b50aa61cf0a429dd6cb2760a08f7f698a50454b), [`f2b7f1f`](https://github.com/browserbase/stagehand/commit/f2b7f1f284eef1f96753319b66c7d0b273a6f8cd), [`c8d672f`](https://github.com/browserbase/stagehand/commit/c8d672f7c410c256defbc2e87ead99239837aa28), [`bebf204`](https://github.com/browserbase/stagehand/commit/bebf2044502333c694743078c5b0c9deae11fb79), [`37d6810`](https://github.com/browserbase/stagehand/commit/37d6810a704773d0383a86f98f5f17c7d5b21975)]:
+ - @browserbasehq/stagehand@2.2.1
diff --git a/packages/core/examples/actionable_observe_example.ts b/packages/core/examples/actionable_observe_example.ts
new file mode 100644
index 000000000..2c2ce8d7f
--- /dev/null
+++ b/packages/core/examples/actionable_observe_example.ts
@@ -0,0 +1,80 @@
+/**
+ * This example shows how to use actionable observe()
+ *
+ * You can use observe to get a cache-able Playwright action as JSON, then pass that JSON to act() to perform the action.
+ *
+ * This is useful for:
+ * - Previewing actions before running them
+ * - Saving actions to a file and replaying them later
+ * - Hiding sensitive information from LLMs
+ *
+ * For more on caching, see: https://docs.stagehand.dev/examples/caching
+ * Also check out the form_filling_sensible.ts example for a more complex example of using observe() to fill out a form.
+ */
+
+import { Action, Stagehand } from "../lib/v3";
+
+async function example() {
+ const stagehand = new Stagehand({
+ env: "BROWSERBASE",
+ verbose: 1,
+ });
+ await stagehand.init();
+ const page = stagehand.context.pages()[0];
+
+ await page.goto("https://www.apartments.com/san-francisco-ca/");
+
+ let observation: Action;
+
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ [observation] = await stagehand.observe("find the 'all filters' button");
+ await stagehand.act(observation);
+
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ [observation] = await stagehand.observe(
+ "find the '1+' button in the 'beds' section",
+ );
+ await stagehand.act(observation);
+
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ [observation] = await stagehand.observe(
+ "find the 'apartments' button in the 'home type' section",
+ );
+ await stagehand.act(observation);
+
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ [observation] = await stagehand.observe(
+ "find the pet policy dropdown to click on.",
+ );
+ await stagehand.act(observation);
+
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ [observation] = await stagehand.observe(
+ "find the 'Dog Friendly' option to click on",
+ );
+ await stagehand.act(observation);
+
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ [observation] = await stagehand.observe("find the 'see results' section");
+ await stagehand.act(observation);
+
+ const currentUrl = page.url();
+ await stagehand.close();
+ if (
+ currentUrl.includes(
+ "https://www.apartments.com/apartments/san-francisco-ca/min-1-bedrooms-pet-friendly-dog/",
+ )
+ ) {
+ console.log("✅ Success! we made it to the correct page");
+ } else {
+ console.log(
+ "❌ Whoops, looks like we didn't make it to the correct page. " +
+ "\nThanks for testing out this new Stagehand feature!" +
+ "\nReach us on Discord if you have any feedback/questions/suggestions!",
+ );
+ }
+}
+
+(async () => {
+ await example();
+})();
diff --git a/packages/core/examples/agent-custom-tools.ts b/packages/core/examples/agent-custom-tools.ts
new file mode 100644
index 000000000..d6b16eea1
--- /dev/null
+++ b/packages/core/examples/agent-custom-tools.ts
@@ -0,0 +1,107 @@
+/**
+ * This example shows how to pass custom tools to stagehand agent (both CUA and non-CUA)
+ */
+import { z } from "zod";
+import { tool } from "ai";
+import { Stagehand } from "../lib/v3";
+import chalk from "chalk";
+
+// Mock weather API, replace with your own API/tool logic
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const fetchWeatherAPI = async (location: string) => {
+ return {
+ temp: 70,
+ conditions: "sunny",
+ };
+};
+
+// Define the tool in an AI SDK format
+const getWeather = tool({
+ description: "Get the current weather in a location",
+ inputSchema: z.object({
+ location: z.string().describe("The location to get weather for"),
+ }),
+ execute: async ({ location }) => {
+ // Your custom logic here
+ const weather = await fetchWeatherAPI(location);
+ return {
+ location,
+ temperature: weather.temp,
+ conditions: weather.conditions,
+ };
+ },
+});
+
+async function main() {
+ console.log(
+ `\n${chalk.bold("Stagehand 🤘 Computer Use Agent (CUA) Demo")}\n`,
+ );
+
+ // Initialize Stagehand
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 2,
+ experimental: true, // You must enable experimental mode to use custom tools / MCP integrations
+ model: "anthropic/claude-sonnet-4-5",
+ });
+ await stagehand.init();
+
+ try {
+ const page = stagehand.context.pages()[0];
+
+ // Create a computer use agent
+ const agent = stagehand.agent({
+ mode: "cua",
+ model: {
+ modelName: "anthropic/claude-sonnet-4-5-20250929",
+ apiKey: process.env.ANTHROPIC_API_KEY,
+ },
+ systemPrompt: `You are a helpful assistant that can use a web browser.
+ You are currently on the following page: ${page.url()}.
+ Do not ask follow up questions, the user will trust your judgement. Today's date is ${new Date().toLocaleDateString()}.`,
+ tools: {
+ getWeather, // Pass the tools to the agent
+ },
+ });
+
+ // const agent = stagehand.agent({
+ // systemPrompt: `You are a helpful assistant that can use a web browser.
+ // You are currently on the following page: ${page.url()}.
+ // Do not ask follow up questions, the user will trust your judgement. Today's date is ${new Date().toLocaleDateString()}.`,
+ // // Pass the tools to the agent
+ // tools: {
+ // getWeather: getWeather,
+ // },
+ // });
+
+ // Navigate to the Browserbase careers page
+ await page.goto("https://www.google.com");
+
+ // Define the instruction for the CUA
+ const instruction = "What's the weather in San Francisco?";
+ console.log(`Instruction: ${chalk.white(instruction)}`);
+
+ // Execute the instruction
+ const result = await agent.execute({
+ instruction,
+ maxSteps: 20,
+ });
+
+ console.log(`${chalk.green("✓")} Execution complete`);
+ console.log(`${chalk.yellow("⤷")} Result:`);
+ console.log(chalk.white(JSON.stringify(result, null, 2)));
+ } catch (error) {
+ console.log(`${chalk.red("✗")} Error: ${error}`);
+ if (error instanceof Error && error.stack) {
+ console.log(chalk.dim(error.stack.split("\n").slice(1).join("\n")));
+ }
+ } finally {
+ // Close the browser
+ await stagehand.close();
+ }
+}
+
+main().catch((error) => {
+ console.log(`${chalk.red("✗")} Unhandled error in main function`);
+ console.log(chalk.red(error));
+});
diff --git a/packages/core/examples/agent_stream_example.ts b/packages/core/examples/agent_stream_example.ts
new file mode 100644
index 000000000..66d3257f8
--- /dev/null
+++ b/packages/core/examples/agent_stream_example.ts
@@ -0,0 +1,49 @@
+import { Stagehand } from "../lib/v3";
+import dotenv from "dotenv";
+import chalk from "chalk";
+
+// Load environment variables
+dotenv.config();
+async function main() {
+ console.log(`\n${chalk.bold("Stagehand 🤘 Agent Streaming Example")}\n`);
+ // Initialize Stagehand
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 0,
+ cacheDir: "stagehand-agent-cache",
+ logInferenceToFile: false,
+ experimental: true,
+ });
+
+ await stagehand.init();
+
+ try {
+ const page = stagehand.context.pages()[0];
+ await page.goto("https://amazon.com");
+
+ // Create a streaming agent with stream: true in the config
+ const agent = stagehand.agent({
+ model: "anthropic/claude-sonnet-4-5-20250929",
+ stream: true, // This makes execute() return AgentStreamResult
+ });
+
+ const agentRun = await agent.execute({
+ instruction: "go to amazon, and search for shampoo, stop after searching",
+ maxSteps: 20,
+ });
+ // stream the text
+ for await (const delta of agentRun.textStream) {
+ process.stdout.write(delta);
+ }
+ // stream everything ( toolcalls, messages, etc.)
+ // for await (const delta of result.fullStream) {
+ // console.log(delta);
+ // }
+
+ const finalResult = await agentRun.result;
+ console.log("Final Result:", finalResult);
+ } catch (error) {
+ console.log(`${chalk.red("✗")} Error: ${error}`);
+ }
+}
+main();
diff --git a/packages/core/examples/cua-example.ts b/packages/core/examples/cua-example.ts
new file mode 100644
index 000000000..327094c2f
--- /dev/null
+++ b/packages/core/examples/cua-example.ts
@@ -0,0 +1,71 @@
+/**
+ * This example shows how to use a computer use agent (CUA) to navigate a web page and extract data.
+ *
+ * To learn more about the CUA, see: https://docs.stagehand.dev/examples/computer_use
+ *
+ * NOTE: YOU MUST CONFIGURE BROWSER DIMENSIONS TO USE COMPUTER USE!
+ * Check out stagehand.config.ts for more information.
+ */
+import { Stagehand } from "../lib/v3";
+import chalk from "chalk";
+
+async function main() {
+ console.log(
+ `\n${chalk.bold("Stagehand 🤘 Computer Use Agent (CUA) Demo")}\n`,
+ );
+
+ // Initialize Stagehand
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 2,
+ });
+ await stagehand.init();
+
+ try {
+ const page = stagehand.context.pages()[0];
+
+ // Create a computer use agent
+ const agent = stagehand.agent({
+ mode: "cua",
+ model: {
+ modelName: "google/gemini-2.5-computer-use-preview-10-2025",
+ apiKey: process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY,
+ },
+ systemPrompt: `You are a helpful assistant that can use a web browser.
+ You are currently on the following page: ${page.url()}.
+ Do not ask follow up questions, the user will trust your judgement. Today's date is ${new Date().toLocaleDateString()}.`,
+ });
+
+ // Navigate to the Browserbase careers page
+ await page.goto("https://www.browserbase.com/careers");
+
+ // Define the instruction for the CUA
+ const instruction =
+ "Apply for the first engineer position with mock data. Don't submit the form. You're on the right page";
+ console.log(`Instruction: ${chalk.white(instruction)}`);
+
+ // Execute the instruction
+ const result = await agent.execute({
+ instruction,
+ maxSteps: 20,
+ });
+ await new Promise((resolve) => setTimeout(resolve, 30000));
+
+ console.log(`${chalk.green("✓")} Execution complete`);
+ console.log(`${chalk.yellow("⤷")} Result:`);
+ console.log(chalk.white(JSON.stringify(result, null, 2)));
+ } catch (error) {
+ console.log(`${chalk.red("✗")} Error: ${error}`);
+ if (error instanceof Error && error.stack) {
+ console.log(chalk.dim(error.stack.split("\n").slice(1).join("\n")));
+ }
+ } finally {
+ // Close the browser
+ await stagehand.close();
+ }
+}
+
+main().catch((error) => {
+ console.log(`${chalk.red("✗")} Unhandled error in main function`);
+ console.log(chalk.red(error));
+});
diff --git a/packages/core/examples/custom_client_aisdk.ts b/packages/core/examples/custom_client_aisdk.ts
new file mode 100644
index 000000000..7d7708574
--- /dev/null
+++ b/packages/core/examples/custom_client_aisdk.ts
@@ -0,0 +1,42 @@
+/**
+ * This example shows how to use the Vercel AI SDK to power the Stagehand LLM Client.
+ *
+ * You will need to reference the AI SDK Client in /external_clients/aisdk.ts
+ *
+ * To learn more about the Vercel AI SDK, see: https://sdk.vercel.ai/docs
+ */
+import { Stagehand } from "../lib/v3";
+import { AISdkClient } from "./external_clients/aisdk";
+import { z } from "zod";
+import { openai } from "@ai-sdk/openai";
+
+async function example() {
+ const stagehand = new Stagehand({
+ env: "BROWSERBASE",
+ verbose: 1,
+ llmClient: new AISdkClient({
+ model: openai("gpt-4o"),
+ }),
+ });
+
+ await stagehand.init();
+ const page = stagehand.context.pages()[0];
+
+ await page.goto("https://news.ycombinator.com");
+
+ const { story } = await stagehand.extract(
+ "extract the title of the top story on the page",
+ z.object({
+ story: z.string().describe("the top story on the page"),
+ }),
+ );
+
+ console.log("The top story is:", story);
+ await stagehand.act("click the first story");
+
+ await stagehand.close();
+}
+
+(async () => {
+ await example();
+})();
diff --git a/examples/langchain.ts b/packages/core/examples/custom_client_langchain.ts
similarity index 50%
rename from examples/langchain.ts
rename to packages/core/examples/custom_client_langchain.ts
index 98fe41d4c..2985cb1b7 100644
--- a/examples/langchain.ts
+++ b/packages/core/examples/custom_client_langchain.ts
@@ -1,35 +1,36 @@
+/**
+ * This example shows how to use the Langchain client with Stagehand.
+ *
+ * You will need to reference the Langchain Client in /external_clients/langchain.ts
+ */
import { z } from "zod";
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/stagehand.config";
+import { Stagehand } from "../lib/v3";
import { LangchainClient } from "./external_clients/langchain";
import { ChatOpenAI } from "@langchain/openai";
async function example() {
const stagehand = new Stagehand({
- ...StagehandConfig,
+ env: "BROWSERBASE",
+ verbose: 1,
llmClient: new LangchainClient(
new ChatOpenAI({
model: "gpt-4o",
}),
),
});
-
await stagehand.init();
- await stagehand.page.goto("https://news.ycombinator.com");
-
- const { story } = await stagehand.page.extract({
- schema: z.object({
+ const page = stagehand.context.pages()[0];
+ await page.goto("https://news.ycombinator.com");
+ const { story } = await stagehand.extract(
+ "extract the title of the top story on the page",
+ z.object({
story: z.string().describe("the top story on the page"),
}),
- });
-
+ );
console.log("The top story is:", story);
-
- await stagehand.page.act("click the first story");
-
+ await stagehand.act("click the first story");
await stagehand.close();
}
-
(async () => {
await example();
})();
diff --git a/examples/external_client.ts b/packages/core/examples/custom_client_openai.ts
similarity index 50%
rename from examples/external_client.ts
rename to packages/core/examples/custom_client_openai.ts
index 63f92256a..e92c4c4ba 100644
--- a/examples/external_client.ts
+++ b/packages/core/examples/custom_client_openai.ts
@@ -1,12 +1,19 @@
-import { Stagehand } from "@/dist";
+/**
+ * This example shows how to use a custom OpenAI client with Stagehand.
+ *
+ * The OpenAI API provides a simple, type-safe, and composable way to build AI applications.
+ *
+ * You will need to reference the Custom OpenAI Client in /external_clients/customOpenAI.ts
+ */
+import { Stagehand } from "../lib/v3";
import { z } from "zod";
import { CustomOpenAIClient } from "./external_clients/customOpenAI";
-import StagehandConfig from "@/stagehand.config";
import OpenAI from "openai";
async function example() {
const stagehand = new Stagehand({
- ...StagehandConfig,
+ env: "BROWSERBASE",
+ verbose: 1,
llmClient: new CustomOpenAIClient({
modelName: "gpt-4o-mini",
client: new OpenAI({
@@ -14,14 +21,15 @@ async function example() {
}),
}),
});
-
await stagehand.init();
- await stagehand.page.goto("https://news.ycombinator.com");
- await stagehand.page.act("click on the 'new' link");
- const headlines = await stagehand.page.extract({
- instruction: "Extract the top 3 stories from the Hacker News homepage.",
- schema: z.object({
+ const page = stagehand.context.pages()[0];
+ await page.goto("https://news.ycombinator.com");
+ await stagehand.act("click on the 'new' link");
+
+ const headlines = await stagehand.extract(
+ "Extract the top 3 stories from the Hacker News homepage.",
+ z.object({
stories: z.array(
z.object({
title: z.string(),
@@ -30,7 +38,7 @@ async function example() {
}),
),
}),
- });
+ );
console.log(headlines);
diff --git a/packages/core/examples/example.ts b/packages/core/examples/example.ts
new file mode 100644
index 000000000..5b2bea64e
--- /dev/null
+++ b/packages/core/examples/example.ts
@@ -0,0 +1,44 @@
+import { Stagehand } from "../lib/v3";
+
+async function example(stagehand: Stagehand) {
+ /**
+ * Add your code here!
+ */
+ const page = stagehand.context.pages()[0];
+ await page.goto(
+ "https://browserbase.github.io/stagehand-eval-sites/sites/iframe-hn/",
+ );
+
+ const { extraction } = await stagehand.extract(
+ "grab the the first title from inside the iframe",
+ );
+ console.log(extraction);
+
+ const page2 = await stagehand.context.newPage();
+ await page2.goto(
+ "https://browserbase.github.io/stagehand-eval-sites/sites/iframe-same-proc/",
+ );
+ await stagehand.extract(
+ "extract the placeholder text on the your name field",
+ { page: page2 },
+ );
+ await stagehand.act("fill the your name field with the text 'John Doe'", {
+ page: page2,
+ });
+ const action2 = await stagehand.observe(
+ "select blue as the favorite color on the dropdown",
+ { page: page2 },
+ );
+ action2.map(async (action) => {
+ await stagehand.act(action);
+ });
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "BROWSERBASE",
+ verbose: 2,
+ });
+ await stagehand.init();
+ await example(stagehand);
+})();
diff --git a/packages/core/examples/external_clients/aisdk.ts b/packages/core/examples/external_clients/aisdk.ts
new file mode 100644
index 000000000..f2e50d9a0
--- /dev/null
+++ b/packages/core/examples/external_clients/aisdk.ts
@@ -0,0 +1,132 @@
+import {
+ CoreAssistantMessage,
+ ModelMessage,
+ CoreSystemMessage,
+ Tool,
+ CoreUserMessage,
+ generateObject,
+ generateText,
+ ImagePart,
+ TextPart,
+} from "ai";
+import type { LanguageModelV2 } from "@ai-sdk/provider";
+import {
+ CreateChatCompletionOptions,
+ LLMClient,
+} from "../../lib/v3/llm/LLMClient";
+import { AvailableModel } from "../../lib/v3/types/public";
+import { ChatCompletion } from "openai/resources";
+
+export class AISdkClient extends LLMClient {
+ public type = "aisdk" as const;
+ private model: LanguageModelV2;
+
+ constructor({ model }: { model: LanguageModelV2 }) {
+ super(model.modelId as AvailableModel);
+ this.model = model;
+ }
+
+ async createChatCompletion({
+ options,
+ }: CreateChatCompletionOptions): Promise {
+ const formattedMessages: ModelMessage[] = options.messages.map(
+ (message) => {
+ if (Array.isArray(message.content)) {
+ if (message.role === "system") {
+ const systemMessage: CoreSystemMessage = {
+ role: "system",
+ content: message.content
+ .map((c) => ("text" in c ? c.text : ""))
+ .join("\n"),
+ };
+ return systemMessage;
+ }
+
+ const contentParts = message.content.map((content) => {
+ if ("image_url" in content) {
+ const imageContent: ImagePart = {
+ type: "image",
+ image: content.image_url.url,
+ };
+ return imageContent;
+ } else {
+ const textContent: TextPart = {
+ type: "text",
+ text: content.text,
+ };
+ return textContent;
+ }
+ });
+
+ if (message.role === "user") {
+ const userMessage: CoreUserMessage = {
+ role: "user",
+ content: contentParts,
+ };
+ return userMessage;
+ } else {
+ const textOnlyParts = contentParts.map((part) => ({
+ type: "text" as const,
+ text: part.type === "image" ? "[Image]" : part.text,
+ }));
+ const assistantMessage: CoreAssistantMessage = {
+ role: "assistant",
+ content: textOnlyParts,
+ };
+ return assistantMessage;
+ }
+ }
+
+ return {
+ role: message.role,
+ content: message.content,
+ };
+ },
+ );
+
+ if (options.response_model) {
+ const response = await generateObject({
+ model: this.model,
+ messages: formattedMessages,
+ schema: options.response_model.schema,
+ });
+
+ return {
+ data: response.object,
+ usage: {
+ prompt_tokens: response.usage.inputTokens ?? 0,
+ completion_tokens: response.usage.outputTokens ?? 0,
+ reasoning_tokens: response.usage.reasoningTokens ?? 0,
+ cached_input_tokens: response.usage.cachedInputTokens ?? 0,
+ total_tokens: response.usage.totalTokens ?? 0,
+ },
+ } as T;
+ }
+
+ const tools: Record = {};
+
+ for (const rawTool of options.tools) {
+ tools[rawTool.name] = {
+ description: rawTool.description,
+ inputSchema: rawTool.parameters,
+ } as Tool;
+ }
+
+ const response = await generateText({
+ model: this.model,
+ messages: formattedMessages,
+ tools,
+ });
+
+ return {
+ data: response.text,
+ usage: {
+ prompt_tokens: response.usage.inputTokens ?? 0,
+ completion_tokens: response.usage.outputTokens ?? 0,
+ reasoning_tokens: response.usage.reasoningTokens ?? 0,
+ cached_input_tokens: response.usage.cachedInputTokens ?? 0,
+ total_tokens: response.usage.totalTokens ?? 0,
+ },
+ } as T;
+ }
+}
diff --git a/examples/external_clients/customOpenAI.ts b/packages/core/examples/external_clients/customOpenAI.ts
similarity index 72%
rename from examples/external_clients/customOpenAI.ts
rename to packages/core/examples/external_clients/customOpenAI.ts
index 6a6d70b3f..381f1038d 100644
--- a/examples/external_clients/customOpenAI.ts
+++ b/packages/core/examples/external_clients/customOpenAI.ts
@@ -5,9 +5,12 @@
* You can just pass in an OpenAI instance to the client and it will work.
*/
-import { AvailableModel, CreateChatCompletionOptions, LLMClient } from "@/dist";
+import {
+ AvailableModel,
+ CreateChatCompletionOptions,
+ LLMClient,
+} from "../../lib/v3";
import OpenAI from "openai";
-import { zodResponseFormat } from "openai/helpers/zod";
import type {
ChatCompletion,
ChatCompletionAssistantMessageParam,
@@ -18,17 +21,10 @@ import type {
ChatCompletionSystemMessageParam,
ChatCompletionUserMessageParam,
} from "openai/resources/chat/completions";
-import { z } from "zod";
-import { CreateChatCompletionResponseError } from "@/types/stagehandErrors";
-
-function validateZodSchema(schema: z.ZodTypeAny, data: unknown) {
- try {
- schema.parse(data);
- return true;
- } catch {
- return false;
- }
-}
+import { CreateChatCompletionResponseError } from "../../lib/v3";
+import { toJsonSchema } from "../../lib/v3/zodCompat";
+import { validateZodSchema } from "../../lib/utils";
+import { ZodSchemaValidationError } from "../../lib/v3/types/public/sdkErrors";
export class CustomOpenAIClient extends LLMClient {
public type = "openai" as const;
@@ -79,12 +75,13 @@ export class CustomOpenAIClient extends LLMClient {
);
}
- let responseFormat = undefined;
+ let responseFormat:
+ | ChatCompletionCreateParamsNonStreaming["response_format"]
+ | undefined;
if (options.response_model) {
- responseFormat = zodResponseFormat(
- options.response_model.schema,
- options.response_model.name,
- );
+ responseFormat = {
+ type: "json_object",
+ };
}
/* eslint-disable */
@@ -165,6 +162,18 @@ export class CustomOpenAIClient extends LLMClient {
return formattedMessage;
});
+ if (options.response_model) {
+ const schemaJson = JSON.stringify(
+ toJsonSchema(options.response_model.schema),
+ null,
+ 2,
+ );
+ formattedMessages.push({
+ role: "user",
+ content: `Respond with valid JSON matching this schema:\n${schemaJson}\n\nDo not include any other text, formatting or markdown in your output. Do not include \`\`\` or \`\`\`json in your response. Only the JSON object itself.`,
+ });
+ }
+
const body: ChatCompletionCreateParamsNonStreaming = {
...openaiOptions,
model: this.modelName,
@@ -175,7 +184,7 @@ export class CustomOpenAIClient extends LLMClient {
function: {
name: tool.name,
description: tool.description,
- parameters: tool.parameters,
+ inputSchema: tool.parameters,
},
type: "function",
})),
@@ -204,9 +213,20 @@ export class CustomOpenAIClient extends LLMClient {
if (!extractedData) {
throw new CreateChatCompletionResponseError("No content in response");
}
- const parsedData = JSON.parse(extractedData);
- if (!validateZodSchema(options.response_model.schema, parsedData)) {
+ let parsedData: unknown;
+ try {
+ parsedData = JSON.parse(extractedData);
+ validateZodSchema(options.response_model.schema, parsedData);
+ } catch (e) {
+ const isParseError = e instanceof SyntaxError;
+ logger({
+ category: "openai",
+ message: isParseError
+ ? "Response is not valid JSON"
+ : "Response failed Zod schema validation",
+ level: 0,
+ });
if (retries > 0) {
return this.createChatCompletion({
options,
@@ -215,7 +235,28 @@ export class CustomOpenAIClient extends LLMClient {
});
}
- throw new CreateChatCompletionResponseError("Invalid response schema");
+ if (e instanceof ZodSchemaValidationError) {
+ logger({
+ category: "openai",
+ message: `Error during chat completion: ${e.message}`,
+ level: 0,
+ auxiliary: {
+ errorDetails: {
+ value: `Message: ${e.message}${e.stack ? "\nStack: " + e.stack : ""}`,
+ type: "string",
+ },
+ requestId: { value: requestId, type: "string" },
+ },
+ });
+ throw new CreateChatCompletionResponseError(e.message);
+ }
+ throw new CreateChatCompletionResponseError(
+ isParseError
+ ? "Failed to parse model response as JSON"
+ : e instanceof Error
+ ? e.message
+ : "Unknown error during response processing",
+ );
}
return {
diff --git a/examples/external_clients/langchain.ts b/packages/core/examples/external_clients/langchain.ts
similarity index 89%
rename from examples/external_clients/langchain.ts
rename to packages/core/examples/external_clients/langchain.ts
index 1d071a63b..7394daeb6 100644
--- a/examples/external_clients/langchain.ts
+++ b/packages/core/examples/external_clients/langchain.ts
@@ -1,6 +1,9 @@
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
-import { CreateChatCompletionOptions, LLMClient, AvailableModel } from "@/dist";
-import { zodToJsonSchema } from "zod-to-json-schema";
+import {
+ CreateChatCompletionOptions,
+ LLMClient,
+ AvailableModel,
+} from "../../lib/v3";
import {
AIMessage,
BaseMessageLike,
@@ -8,6 +11,7 @@ import {
SystemMessage,
} from "@langchain/core/messages";
import { ChatCompletion } from "openai/resources";
+import { toJsonSchema } from "../../lib/v3/zodCompat";
export class LangchainClient extends LLMClient {
public type = "langchainClient" as const;
@@ -56,9 +60,8 @@ export class LangchainClient extends LLMClient {
);
if (options.response_model) {
- const responseSchema = zodToJsonSchema(options.response_model.schema, {
- $refStrategy: "none",
- });
+ //ref string no longer needed, this is now default behavior
+ const responseSchema = toJsonSchema(options.response_model.schema);
const structuredModel = this.model.withStructuredOutput(responseSchema);
const response = await structuredModel.invoke(formattedMessages);
diff --git a/examples/form_filling_sensible.ts b/packages/core/examples/form_filling_sensible.ts
similarity index 73%
rename from examples/form_filling_sensible.ts
rename to packages/core/examples/form_filling_sensible.ts
index e2de10dd7..763215bf7 100644
--- a/examples/form_filling_sensible.ts
+++ b/packages/core/examples/form_filling_sensible.ts
@@ -1,32 +1,29 @@
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/stagehand.config";
+/**
+ * This example shows you how to use observe() to get a cacheable Playwright action as JSON, then pass that JSON to act() to perform the action.
+ *
+ * In this specific example, we use observe() to get multiple actions, then iterate through each action to fill the form with sensitive data at lightning speed.
+ */
+import { Stagehand } from "../lib/v3";
import chalk from "chalk";
async function formFillingSensible() {
const stagehand = new Stagehand({
- ...StagehandConfig,
- // Uncomment the following lines to run locally or use a different model
- env: "LOCAL",
- modelName: "gpt-4o-mini",
+ env: "BROWSERBASE",
+ verbose: 1,
});
await stagehand.init();
-
- // Block manifest worker to prevent PWA installation popup.
- // This is necessary because the website prompts the user to install the PWA and prevents form filling.
- await stagehand.page.route("**/manifest.json", (route) => route.abort());
+ const page = stagehand.context.pages()[0];
// Go to the website and wait for it to load
- await stagehand.page.goto("https://file.1040.com/estimate/", {
+ await page.goto("https://file.1040.com/estimate/", {
waitUntil: "networkidle",
- timeout: 30000,
+ timeoutMs: 30000,
});
// Observe the form fields with suggested actions
- const observed = await stagehand.page.observe({
- instruction:
- "fill all the form fields in the page with mock data. In the description inlcude the field name",
- returnAction: true,
- });
+ const observed = await stagehand.observe(
+ "fill all the form fields in the page with mock data. In the description include the field name",
+ );
// Uncomment the following snippet to see the stagehand candidate suggestions (initial)
console.log(
@@ -82,7 +79,7 @@ async function formFillingSensible() {
// Fill all the form fields with the sensible candidates
for (const candidate of updatedFields) {
- await stagehand.page.act(candidate);
+ await stagehand.act(candidate);
}
}
diff --git a/packages/core/examples/google_enter.ts b/packages/core/examples/google_enter.ts
new file mode 100644
index 000000000..2bd3c3e32
--- /dev/null
+++ b/packages/core/examples/google_enter.ts
@@ -0,0 +1,24 @@
+/**
+ * This example shows how to use the Stagehand agent to navigate to Google and search for "Browserbase".
+ *
+ * It's mainly meant to sanity check using page.act() to press enter, since some LLMs have issues with it.
+ */
+
+import { Stagehand } from "../lib/v3";
+
+async function example() {
+ const stagehand = new Stagehand({
+ env: "BROWSERBASE",
+ verbose: 1,
+ });
+ await stagehand.init();
+ const page = stagehand.context.pages()[0];
+ await page.goto("https://google.com");
+ await stagehand.act("type in 'Browserbase'");
+ await stagehand.act("press enter");
+ await stagehand.close();
+}
+
+(async () => {
+ await example();
+})();
diff --git a/examples/instructions.ts b/packages/core/examples/instructions.ts
similarity index 55%
rename from examples/instructions.ts
rename to packages/core/examples/instructions.ts
index 250199ba1..465097523 100644
--- a/examples/instructions.ts
+++ b/packages/core/examples/instructions.ts
@@ -1,28 +1,23 @@
/**
- * This example shows how to use custom instructions with Stagehand.
+ * This example shows how to use custom system prompts with Stagehand.
*/
-import { Stagehand } from "@/dist";
-import StagehandConfig from "@/stagehand.config";
+import { Stagehand } from "../lib/v3";
async function example() {
const stagehand = new Stagehand({
- ...StagehandConfig,
+ env: "BROWSERBASE",
+ verbose: 1,
systemPrompt:
"if the users says `secret12345`, click on the 'getting started' tab. additionally, if the user says to type something, translate their input into french and type it.",
});
await stagehand.init();
- const page = stagehand.page;
-
+ const page = stagehand.context.pages()[0];
await page.goto("https://docs.browserbase.com/");
- await page.act({
- action: "secret12345",
- });
+ await stagehand.act("secret12345");
- await page.act({
- action: "search for 'how to use browserbase'",
- });
+ await stagehand.act("search for 'how to use browserbase'");
await stagehand.close();
}
diff --git a/packages/core/examples/integrations/exa.ts b/packages/core/examples/integrations/exa.ts
new file mode 100644
index 000000000..46cc6da2a
--- /dev/null
+++ b/packages/core/examples/integrations/exa.ts
@@ -0,0 +1,42 @@
+import { Stagehand } from "../../lib/v3";
+
+async function example(stagehand: Stagehand) {
+ const page = stagehand.context.pages()[0];
+ await page.goto("https://www.google.com");
+
+ const agent = stagehand.agent({
+ integrations: [
+ `https://mcp.exa.ai/mcp?exaApiKey=${process.env.EXA_API_KEY}`,
+ ],
+ // Optional: Add custom instructions
+ systemPrompt: `You are a helpful assistant that can use a browser as well as external tools such as web search.
+ You have access to the Exa search tool to find information on the web.
+ When looking for products to buy, make sure to search for current and reliable information.
+ Be thorough in your research before making purchase decisions.`,
+ });
+
+ const result = await agent.execute(
+ "Use one of the tools from Exa to search for the top headphones of 2025. After doing so, use the browser and go through the checkout flow for the best one.",
+ );
+
+ console.log(result);
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ model: "openai/gpt-4.1",
+ verbose: 1,
+ logInferenceToFile: true,
+ experimental: true,
+ });
+
+ try {
+ await stagehand.init();
+ await example(stagehand);
+ } catch (error) {
+ console.error("Error running example:", error);
+ } finally {
+ await stagehand.close();
+ }
+})();
diff --git a/packages/core/examples/integrations/supabase.ts b/packages/core/examples/integrations/supabase.ts
new file mode 100644
index 000000000..b8bce331e
--- /dev/null
+++ b/packages/core/examples/integrations/supabase.ts
@@ -0,0 +1,37 @@
+import { connectToMCPServer, Stagehand } from "../../lib/v3";
+
+async function example(stagehand: Stagehand) {
+ const page = stagehand.context.pages()[0];
+ await page.goto("https://www.opentable.com/");
+
+ const supabaseClient = await connectToMCPServer(
+ `https://server.smithery.ai/@supabase-community/supabase-mcp/mcp?api_key=${process.env.SMITHERY_API_KEY}`,
+ );
+
+ const agent = stagehand.agent({
+ model: "openai/computer-use-preview",
+ integrations: [supabaseClient],
+ });
+
+ const result = await agent.execute(
+ "Search for restaurants in New Brunswick, NJ. Then, use the Supabase tools to insert the name of the first result of the search into a table called 'restaurants'.",
+ );
+
+ console.log(result);
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 1,
+ });
+
+ try {
+ await stagehand.init();
+ await example(stagehand);
+ } catch (error) {
+ console.error("Error running example:", error);
+ } finally {
+ await stagehand.close();
+ }
+})();
diff --git a/packages/core/examples/mcp.ts b/packages/core/examples/mcp.ts
new file mode 100644
index 000000000..652260859
--- /dev/null
+++ b/packages/core/examples/mcp.ts
@@ -0,0 +1,75 @@
+// import { Stagehand } from "../lib/v3";
+// import StagehandConfig from "@/stagehand.config";
+// import chalk from "chalk";
+// import { connectToMCPServer } from "../lib/mcp/connection";
+
+// async function main() {
+// console.log(`\n${chalk.bold("Stagehand 🤘 MCP Demo")}\n`);
+// console.log(process.env.NOTION_TOKEN);
+
+// // Initialize Stagehand
+// const stagehand = new Stagehand({
+// ...StagehandConfig,
+// env: "LOCAL",
+// experimental: true,
+// });
+// await stagehand.init();
+
+// const notionClient = await connectToMCPServer({
+// command: "npx",
+// args: ["-y", "@notionhq/notion-mcp-server"],
+// env: {
+// NOTION_TOKEN: process.env.NOTION_TOKEN,
+// },
+// });
+
+// try {
+// const page = stagehand.page;
+
+// // Create a computer use agent
+// const agent = stagehand.agent({
+// provider: "anthropic",
+// // For Anthropic, use claude-sonnet-4-20250514 or claude-3-7-sonnet-latest
+// model: "claude-sonnet-4-20250514",
+// instructions: `You are a helpful assistant that can use a web browser.
+// You are currently on the following page: ${page.url()}.
+// Do not ask follow up questions, the user will trust your judgement.
+// You have access to the Notion MCP.`,
+// options: {
+// apiKey: process.env.ANTHROPIC_API_KEY,
+// },
+// integrations: [notionClient],
+// });
+
+// // Navigate to the Browserbase careers page
+// await page.goto("https://www.google.com");
+
+// // Define the instruction for the CUA
+// const instruction =
+// "Check the Agent Tasks page in notion, read your tasks, perform them and update the notion page with the results.";
+// console.log(`Instruction: ${chalk.white(instruction)}`);
+
+// // Execute the instruction
+// const result = await agent.execute({
+// instruction,
+// maxSteps: 50,
+// });
+
+// console.log(`${chalk.green("✓")} Execution complete`);
+// console.log(`${chalk.yellow("⤷")} Result:`);
+// console.log(chalk.white(JSON.stringify(result, null, 2)));
+// } catch (error) {
+// console.log(`${chalk.red("✗")} Error: ${error}`);
+// if (error instanceof Error && error.stack) {
+// console.log(chalk.dim(error.stack.split("\n").slice(1).join("\n")));
+// }
+// } finally {
+// // Close the browser
+// await stagehand.close();
+// }
+// }
+
+// main().catch((error) => {
+// console.log(`${chalk.red("✗")} Unhandled error in main function`);
+// console.log(chalk.red(error));
+// });
diff --git a/examples/operator-example.ts b/packages/core/examples/operator-example.ts
similarity index 52%
rename from examples/operator-example.ts
rename to packages/core/examples/operator-example.ts
index 5978e8d72..820f26341 100644
--- a/examples/operator-example.ts
+++ b/packages/core/examples/operator-example.ts
@@ -1,35 +1,38 @@
-import { LogLine, Stagehand } from "@/dist";
+/**
+ * This example shows how to use the Stagehand operator to do simple autonomous tasks.
+ *
+ * This is built off of our open source project, Open Operator: https://operator.browserbase.com
+ *
+ * To learn more about Stagehand Agents, see: https://docs.stagehand.dev/concepts/agent
+ */
+import { Stagehand } from "../lib/v3";
import dotenv from "dotenv";
-import StagehandConfig from "@/stagehand.config";
import chalk from "chalk";
// Load environment variables
dotenv.config();
-const INSTRUCTION =
- "Go to Google Japan and interact with it in Japanese. Tell me (in English) an authentic recipe that I can make with ingredients found in American grocery stores.";
-
async function main() {
console.log(`\n${chalk.bold("Stagehand 🤘 Operator Example")}\n`);
-
// Initialize Stagehand
const stagehand = new Stagehand({
- ...StagehandConfig,
- logger: ({ level, message, timestamp }: LogLine) => {
- console.log({ level, message, timestamp });
- },
+ env: "LOCAL",
+ verbose: 2,
+ cacheDir: "stagehand-agent-cache",
+ logInferenceToFile: false,
});
await stagehand.init();
try {
+ const page = stagehand.context.pages()[0];
+ await page.goto(
+ "https://browserbase.github.io/stagehand-eval-sites/sites/shadow-dom/",
+ );
const agent = stagehand.agent();
- // Execute the agent
- console.log(`${chalk.cyan("↳")} Instruction: ${INSTRUCTION}`);
-
const result = await agent.execute({
- instruction: INSTRUCTION,
+ instruction: "click the button",
maxSteps: 20,
});
@@ -40,8 +43,7 @@ async function main() {
} catch (error) {
console.log(`${chalk.red("✗")} Error: ${error}`);
} finally {
- await stagehand.close();
+ // await stagehand.close();
}
}
-
main();
diff --git a/packages/core/examples/oss-cua-example.ts b/packages/core/examples/oss-cua-example.ts
new file mode 100644
index 000000000..76549d936
--- /dev/null
+++ b/packages/core/examples/oss-cua-example.ts
@@ -0,0 +1,83 @@
+/**
+ * This example shows how to use a computer use agent (CUA) to navigate a web page and extract data.
+ *
+ * To learn more about the CUA, see: https://docs.stagehand.dev/examples/computer_use
+ *
+ * NOTE: YOU MUST CONFIGURE BROWSER DIMENSIONS TO USE COMPUTER USE!
+ * Check out stagehand.config.ts for more information.
+ */
+import { Stagehand } from "../lib/v3";
+import chalk from "chalk";
+
+async function main() {
+ console.log(
+ `\n${chalk.bold("Stagehand 🤘 Computer Use Agent (CUA) Demo")}\n`,
+ );
+
+ // Initialize Stagehand
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 2,
+ localBrowserLaunchOptions: {
+ viewport: {
+ width: 1288,
+ height: 711,
+ },
+ deviceScaleFactor: 1,
+ },
+ });
+ await stagehand.init();
+
+ try {
+ const page = stagehand.context.pages()[0];
+
+ // Create a computer use agent
+ const agent = stagehand.agent({
+ mode: "cua",
+ model: {
+ modelName: "microsoft/fara-7b",
+ apiKey: process.env.AZURE_API_KEY,
+ baseURL: process.env.AZURE_ENDPOINT,
+ /** Alternative model configuration for Fireworks Deployments */
+ // modelName: "accounts/...",
+ // apiKey: process.env.FIREWORKS_API_KEY,
+ // baseURL: "https://api.fireworks.ai/inference/v1",
+ // provider: "microsoft", // Important: this routes to the MicrosoftCUAClient
+ },
+ systemPrompt: `You are a helpful assistant that can use a web browser.
+ You are currently on the following page: ${page.url()}.
+ Do not ask follow up questions, the user will trust your judgement. Today's date is ${new Date().toLocaleDateString()}. Remember apply buttons are there for a reason.`,
+ });
+
+ // Navigate to the Browserbase careers page
+ await page.goto("https://www.browserbase.com/careers");
+
+ // Define the instruction for the CUA
+ const instruction = `Apply for the first engineer position with mock data on the ${page.url()} page. Don't submit the form.`;
+ console.log(`Instruction: ${chalk.white(instruction)}`);
+
+ // Execute the instruction
+ const result = await agent.execute({
+ instruction,
+ maxSteps: 20,
+ });
+ await new Promise((resolve) => setTimeout(resolve, 30000));
+
+ console.log(`${chalk.green("✓")} Execution complete`);
+ console.log(`${chalk.yellow("⤷")} Result:`);
+ console.log(chalk.white(JSON.stringify(result, null, 2)));
+ } catch (error) {
+ console.log(`${chalk.red("✗")} Error: ${error}`);
+ if (error instanceof Error && error.stack) {
+ console.log(chalk.dim(error.stack.split("\n").slice(1).join("\n")));
+ }
+ } finally {
+ // Close the browser
+ await stagehand.close();
+ }
+}
+
+main().catch((error) => {
+ console.log(`${chalk.red("✗")} Unhandled error in main function`);
+ console.log(chalk.red(error));
+});
diff --git a/examples/parameterizeApiKey.ts b/packages/core/examples/parameterizeApiKey.ts
similarity index 64%
rename from examples/parameterizeApiKey.ts
rename to packages/core/examples/parameterizeApiKey.ts
index 7af1c3eb1..5da677321 100644
--- a/examples/parameterizeApiKey.ts
+++ b/packages/core/examples/parameterizeApiKey.ts
@@ -1,4 +1,4 @@
-import { Stagehand } from "@/dist";
+import { Stagehand } from "../lib/v3";
import { z } from "zod";
/**
@@ -15,23 +15,23 @@ async function example() {
const stagehand = new Stagehand({
env: "LOCAL",
verbose: 1,
- enableCaching: false,
- modelName: "gpt-4o",
- modelClientOptions: {
+ model: {
+ modelName: "gpt-4o",
apiKey: process.env.USE_OPENAI_API_KEY,
},
});
await stagehand.init();
- await stagehand.page.goto("https://github.com/browserbase/stagehand");
- await stagehand.page.act({ action: "click on the contributors" });
- const contributor = await stagehand.page.extract({
- instruction: "extract the top contributor",
- schema: z.object({
+ const page = stagehand.context.pages()[0];
+ await page.goto("https://github.com/browserbase/stagehand");
+ await stagehand.act("click on the contributors");
+ const contributor = await stagehand.extract(
+ "extract the top contributor",
+ z.object({
username: z.string(),
url: z.string(),
}),
- });
+ );
console.log(`Our favorite contributor is ${contributor.username}`);
}
diff --git a/packages/core/examples/tsconfig.json b/packages/core/examples/tsconfig.json
new file mode 100644
index 000000000..77268eb1d
--- /dev/null
+++ b/packages/core/examples/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "module": "ESNext",
+ "target": "ES2022",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true
+ },
+ "include": ["*.ts"],
+ "exclude": ["node_modules"],
+ "ts-node": {
+ "esm": true
+ }
+}
diff --git a/packages/core/examples/v3/cuaReplay.ts b/packages/core/examples/v3/cuaReplay.ts
new file mode 100644
index 000000000..d729fb593
--- /dev/null
+++ b/packages/core/examples/v3/cuaReplay.ts
@@ -0,0 +1,97 @@
+import { Stagehand } from "../../lib/v3";
+import { v3Logger } from "../../lib/v3/logger";
+import dotenv from "dotenv";
+
+dotenv.config();
+
+async function runDemo(runNumber: number) {
+ const startTime = Date.now();
+
+ v3Logger({
+ level: 1,
+ category: "demo",
+ message: `RUN ${runNumber}: ${runNumber === 1 ? "BUILDING CACHE" : "USING CACHE"}`,
+ });
+
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 1,
+ cacheDir: "cua-agent-cache",
+ });
+
+ await stagehand.init();
+
+ const page = stagehand.context.pages()[0];
+
+ await page.goto("https://v0-modern-login-flow.vercel.app/", {
+ waitUntil: "networkidle",
+ });
+
+ const agent = stagehand.agent({
+ mode: "cua",
+ model: "anthropic/claude-sonnet-4-20250514",
+ });
+
+ const result = await agent.execute({
+ instruction: `Sign in with the email address 'test@browserbaser.com' and the password 'stagehand=goated'`,
+ maxSteps: 20,
+ });
+
+ const endTime = Date.now();
+ const duration = (endTime - startTime) / 1000;
+
+ await stagehand.close();
+
+ return {
+ duration,
+ success: result.success,
+ result,
+ };
+}
+
+async function main() {
+ const metrics1 = await runDemo(1);
+
+ v3Logger({
+ level: 1,
+ category: "demo",
+ message: "⏳ Waiting 2 seconds before cached run...",
+ });
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+
+ v3Logger({
+ level: 1,
+ category: "demo",
+ message: "Starting second run with cache...",
+ });
+ const metrics2 = await runDemo(2);
+
+ const duration1 = `${metrics1.duration.toFixed(2)}s`;
+ const duration2 = `${metrics2.duration.toFixed(2)}s`;
+
+ v3Logger({
+ level: 1,
+ category: "demo",
+ message: `
+╔════════════════════════════════════════════════════════════╗
+║ 📊 PERFORMANCE COMPARISON ║
+╚════════════════════════════════════════════════════════════╝
+
+┌─────────────────────┬──────────────────┬──────────────────┐
+│ Metric │ Run 1 (Cold) │ Run 2 (Cached) │
+├─────────────────────┼──────────────────┼──────────────────┤
+│ Duration │ ${duration1.padEnd(16)} │ ${duration2.padEnd(16)} │
+└─────────────────────┴──────────────────┴──────────────────┘
+
+ Performance Comparison:
+ • Speed: ${((1 - metrics2.duration / metrics1.duration) * 100).toFixed(1)}% faster with cache
+ • Time saved: ${(metrics1.duration - metrics2.duration).toFixed(2)} seconds
+
+ Insights:
+ • First run establishes the CUA action cache
+ • Second run reuses cached actions for instant execution
+ • Zero LLM tokens used on cached run`,
+ });
+}
+
+main().catch(console.error);
diff --git a/packages/core/examples/v3/deepLocator.ts b/packages/core/examples/v3/deepLocator.ts
new file mode 100644
index 000000000..322fbadb8
--- /dev/null
+++ b/packages/core/examples/v3/deepLocator.ts
@@ -0,0 +1,30 @@
+import { Stagehand } from "../../lib/v3";
+
+async function example(stagehand: Stagehand) {
+ const page = stagehand.context.pages()[0];
+ await page.goto(
+ "https://browserbase.github.io/stagehand-eval-sites/sites/oopif-in-closed-shadow-dom/",
+ );
+
+ // crossing OOPIF & shadow root boundaries with deep locator
+ await page
+ .deepLocator(
+ "/html/body/shadow-host//section/iframe/html/body/main/section[1]/form/div/div[1]/input",
+ )
+ .fill("nunya");
+ await page
+ .deepLocator(
+ "/html/body/shadow-host//section/iframe/html/body/main/section[1]/form/div/div[2]/input",
+ )
+ .fill("business");
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 0,
+ model: "openai/gpt-4.1",
+ });
+ await stagehand.init();
+ await example(stagehand);
+})();
diff --git a/packages/core/examples/v3/dropdown.ts b/packages/core/examples/v3/dropdown.ts
new file mode 100644
index 000000000..28d8e1368
--- /dev/null
+++ b/packages/core/examples/v3/dropdown.ts
@@ -0,0 +1,32 @@
+import { Stagehand } from "../../lib/v3";
+
+async function example(stagehand: Stagehand) {
+ const page = stagehand.context.pages()[0];
+ await page.goto(
+ "https://browserbase.github.io/stagehand-eval-sites/sites/scroll-dropdown/",
+ );
+
+ const actResult = await stagehand.act(
+ "choose 'Peach' from the favorite colour dropdown",
+ );
+
+ const numSteps = actResult.actions.length;
+
+ console.log(
+ `\n\nThis act() call took ${numSteps} steps. Here are the actions:`,
+ );
+
+ for (const action of actResult.actions) {
+ console.log(`\naction: `, action);
+ }
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 0,
+ model: "google/gemini-2.5-flash",
+ });
+ await stagehand.init();
+ await example(stagehand);
+})();
diff --git a/packages/core/examples/v3/highlight.ts b/packages/core/examples/v3/highlight.ts
new file mode 100644
index 000000000..a2f122207
--- /dev/null
+++ b/packages/core/examples/v3/highlight.ts
@@ -0,0 +1,27 @@
+import { Stagehand } from "../../lib/v3";
+
+async function example(stagehand: Stagehand) {
+ const page = stagehand.context.pages()[0];
+ await page.goto(
+ "https://browserbase.github.io/stagehand-eval-sites/sites/closed-shadow-root-in-oopif/",
+ );
+
+ await page
+ .deepLocator(
+ "xpath=/html/body/main/section/iframe/html/body/shadow-demo//div/button",
+ )
+ .highlight({
+ durationMs: 20000,
+ contentColor: { r: 255, g: 0, b: 0 },
+ });
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 0,
+ model: "google/gemini-2.5-flash",
+ });
+ await stagehand.init();
+ await example(stagehand);
+})();
diff --git a/packages/core/examples/v3/patchright.ts b/packages/core/examples/v3/patchright.ts
new file mode 100644
index 000000000..023cc726c
--- /dev/null
+++ b/packages/core/examples/v3/patchright.ts
@@ -0,0 +1,32 @@
+import { Stagehand } from "../../lib/v3";
+import { chromium } from "patchright-core";
+import { z } from "zod";
+
+async function example(stagehand: Stagehand) {
+ const browser = await chromium.connectOverCDP({
+ wsEndpoint: stagehand.connectURL(),
+ });
+
+ const prContext = browser.contexts()[0];
+ const prPage = prContext.pages()[0];
+ await prPage.goto("https://github.com/microsoft/playwright/issues/30261");
+
+ await stagehand.act("scroll to the bottom of the page", { page: prPage });
+
+ const reason = await stagehand.extract(
+ "extract the reason why playwright doesn't expose frame IDs",
+ z.string(),
+ // page arg not required
+ );
+ console.log(reason);
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 0,
+ model: "openai/gpt-4.1",
+ });
+ await stagehand.init();
+ await example(stagehand);
+})();
diff --git a/packages/core/examples/v3/playwright.ts b/packages/core/examples/v3/playwright.ts
new file mode 100644
index 000000000..dce44dc99
--- /dev/null
+++ b/packages/core/examples/v3/playwright.ts
@@ -0,0 +1,41 @@
+import { Stagehand } from "../../lib/v3";
+import { chromium } from "playwright-core";
+import { z } from "zod";
+
+async function example(stagehand: Stagehand) {
+ const browser = await chromium.connectOverCDP({
+ wsEndpoint: stagehand.connectURL(),
+ });
+ const pwContext = browser.contexts()[0];
+ const pwPage1 = pwContext.pages()[0];
+ await pwPage1.goto("https://docs.stagehand.dev/first-steps/introduction");
+
+ const pwPage2 = await pwContext.newPage();
+ await pwPage2.goto("https://docs.stagehand.dev/configuration/observability");
+
+ const [page1Extraction, page2Extraction] = await Promise.all([
+ stagehand.extract(
+ "extract the names of the four stagehand primitives",
+ z.array(z.string()),
+ { page: pwPage1 },
+ ),
+ stagehand.extract(
+ "extract the list of session dashboard features",
+ z.array(z.string()),
+ { page: pwPage2 },
+ ),
+ ]);
+
+ console.log(page1Extraction);
+ console.log(page2Extraction);
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "BROWSERBASE",
+ verbose: 1,
+ model: "openai/gpt-4.1",
+ });
+ await stagehand.init();
+ await example(stagehand);
+})();
diff --git a/packages/core/examples/v3/puppeteer.ts b/packages/core/examples/v3/puppeteer.ts
new file mode 100644
index 000000000..dcebc3316
--- /dev/null
+++ b/packages/core/examples/v3/puppeteer.ts
@@ -0,0 +1,29 @@
+import { Stagehand } from "../../lib/v3";
+import puppeteer from "puppeteer-core";
+
+async function example(stagehand: Stagehand) {
+ const browser = await puppeteer.connect({
+ browserWSEndpoint: stagehand.connectURL(),
+ defaultViewport: null,
+ });
+ const ppPages = await browser.pages();
+ const ppPage = ppPages[0];
+
+ await ppPage.goto("https://www.browserbase.com/blog");
+
+ const actions = await stagehand.observe("find the next page button", {
+ page: ppPage,
+ });
+
+ await stagehand.act(actions[0]);
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 0,
+ model: "openai/gpt-4.1",
+ });
+ await stagehand.init();
+ await example(stagehand);
+})();
diff --git a/packages/core/examples/v3/recordVideo.ts b/packages/core/examples/v3/recordVideo.ts
new file mode 100644
index 000000000..23c355874
--- /dev/null
+++ b/packages/core/examples/v3/recordVideo.ts
@@ -0,0 +1,65 @@
+import path from "node:path";
+import { mkdir } from "node:fs/promises";
+import { Stagehand } from "../../lib/v3";
+import { chromium } from "playwright-core";
+import { z } from "zod";
+
+async function recordPlaywrightVideo(stagehand: Stagehand): Promise {
+ const browser = await chromium.connectOverCDP({
+ wsEndpoint: stagehand.connectURL(),
+ });
+
+ const videoDir = path.resolve(process.cwd(), "artifacts", "stagehand-videos");
+ await mkdir(videoDir, { recursive: true });
+
+ const context = await browser.newContext({
+ recordVideo: {
+ dir: videoDir,
+ size: { width: 1280, height: 720 },
+ },
+ });
+
+ const page = await context.newPage();
+ await page.goto("https://docs.stagehand.dev/first-steps/quickstart", {
+ waitUntil: "domcontentloaded",
+ });
+
+ await stagehand.act("click the introduction div in the first steps section");
+
+ const { primitives } = await stagehand.extract(
+ "list the four Stagehand primitives that are described on the page",
+ z.object({
+ primitives: z.array(z.string()),
+ }),
+ { page },
+ );
+
+ console.log("Stagehand primitives:", primitives.join(", "));
+
+ // Capture the handle before closing the context so we can read the video path afterwards.
+ const video = page.video();
+
+ await context.close();
+
+ if (video) {
+ const videoPath = await video.path();
+ console.log(`Playwright saved the video to ${videoPath}`);
+ } else {
+ console.log("Video recording was not enabled for this context.");
+ }
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 1,
+ model: "google/gemini-2.5-flash",
+ });
+
+ try {
+ await stagehand.init();
+ await recordPlaywrightVideo(stagehand);
+ } finally {
+ await stagehand.close().catch(() => {});
+ }
+})();
diff --git a/packages/core/examples/v3/returnXpath.ts b/packages/core/examples/v3/returnXpath.ts
new file mode 100644
index 000000000..fdeb121ff
--- /dev/null
+++ b/packages/core/examples/v3/returnXpath.ts
@@ -0,0 +1,23 @@
+import { Stagehand } from "../../lib/v3";
+
+async function example(stagehand: Stagehand) {
+ const page = stagehand.context.pages()[0];
+ await page.goto(
+ "https://browserbase.github.io/stagehand-eval-sites/sites/oopif-in-closed-shadow-dom/",
+ );
+
+ const xpath = await page.click(286, 628, { returnXpath: true });
+
+ // use the xpath that was returned from out coord click
+ await page.deepLocator(xpath).fill("hellooooooooo");
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 0,
+ model: "openai/gpt-4.1",
+ });
+ await stagehand.init();
+ await example(stagehand);
+})();
diff --git a/packages/core/examples/v3/shadowRoot.ts b/packages/core/examples/v3/shadowRoot.ts
new file mode 100644
index 000000000..672cea774
--- /dev/null
+++ b/packages/core/examples/v3/shadowRoot.ts
@@ -0,0 +1,29 @@
+import { Stagehand } from "../../lib/v3";
+
+async function example(stagehand: Stagehand) {
+ const page = stagehand.context.pages()[0];
+ await page.goto(
+ "https://browserbase.github.io/stagehand-eval-sites/sites/shadow-dom-closed/",
+ );
+
+ // clicking in closed mode shadow root with an xpath
+ await page.locator("/html/body/shadow-demo//div/button").click();
+
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+
+ await page.reload();
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+
+ // clicking in closed mode shadow root with css selector
+ await page.locator("div > button").click();
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 0,
+ model: "openai/gpt-4.1",
+ });
+ await stagehand.init();
+ await example(stagehand);
+})();
diff --git a/packages/core/examples/v3/targetedExtract.ts b/packages/core/examples/v3/targetedExtract.ts
new file mode 100644
index 000000000..c95c67a99
--- /dev/null
+++ b/packages/core/examples/v3/targetedExtract.ts
@@ -0,0 +1,35 @@
+import { Stagehand } from "../../lib/v3";
+import { z } from "zod";
+
+async function example(stagehand: Stagehand) {
+ const page = stagehand.context.pages()[0];
+ await page.goto(
+ "https://ambarc.github.io/web-element-test/stagehand-breaking-test.html",
+ );
+
+ await page
+ .deepLocator("/html/body/div[2]/div[3]/iframe/html/body/p")
+ .highlight({
+ durationMs: 5000,
+ contentColor: { r: 255, g: 0, b: 0 },
+ });
+
+ const reason = await stagehand.extract(
+ "extract the reason why script injection fails",
+ z.string(),
+ // selector: "// body > div.test-container > div:nth-child(3) > iframe >> body > p:nth-child(3)",
+ { selector: "/html/body/div[2]/div[3]/iframe/html/body/p[2]" },
+ );
+ console.log(reason);
+}
+
+(async () => {
+ const stagehand = new Stagehand({
+ env: "LOCAL",
+ verbose: 0,
+ model: "openai/gpt-4.1",
+ logInferenceToFile: true,
+ });
+ await stagehand.init();
+ await example(stagehand);
+})();
diff --git a/packages/core/examples/v3/v3_agent.ts b/packages/core/examples/v3/v3_agent.ts
new file mode 100644
index 000000000..33d9bc31b
--- /dev/null
+++ b/packages/core/examples/v3/v3_agent.ts
@@ -0,0 +1,50 @@
+import chalk from "chalk";
+import { V3 } from "../../lib/v3";
+
+const INSTRUCTION = "scroll down and click on the last hn story";
+
+async function main() {
+ console.log(`\n${chalk.bold("Stagehand V3 🤘 Operator Example")}\n`);
+
+ // Initialize Stagehand
+ const v3 = new V3({
+ env: "LOCAL",
+ verbose: 2,
+ });
+
+ await v3.init();
+
+ try {
+ const startPage = v3.context.pages()[0];
+ await startPage.goto(
+ "https://browserbase.github.io/stagehand-eval-sites/sites/iframe-hn/",
+ );
+ const agent = v3.agent({
+ cua: false,
+ model: "google/gemini-2.0-flash",
+ executionModel: "google/gemini-2.0-flash",
+ });
+ // {
+ // model: "computer-use-preview-2025-03-11",
+ // provider: "openai",
+ // }
+
+ // Execute the agent
+ console.log(`${chalk.cyan("↳")} Instruction: ${INSTRUCTION}`);
+ const result = await agent.execute({
+ instruction: INSTRUCTION,
+ maxSteps: 20,
+ });
+
+ console.log(`${chalk.green("✓")} Execution complete`);
+ console.log(`${chalk.yellow("⤷")} Result:`);
+ console.log(JSON.stringify(result, null, 2));
+ console.log(chalk.white(result.message));
+ } catch (error) {
+ console.log(`${chalk.red("✗")} Error: ${error}`);
+ } finally {
+ // await v3.close();
+ }
+}
+
+main();
diff --git a/packages/core/examples/v3_example.ts b/packages/core/examples/v3_example.ts
new file mode 100644
index 000000000..a30f30e9d
--- /dev/null
+++ b/packages/core/examples/v3_example.ts
@@ -0,0 +1,36 @@
+import { V3 } from "../lib/v3";
+import { z } from "zod";
+
+async function example(v3: V3) {
+ const page = v3.context.pages()[0];
+ await page.goto("https://www.apartments.com/san-francisco-ca/2-bedrooms/", {
+ waitUntil: "load",
+ });
+ const apartment_listings = await v3.extract(
+ "Extract all the apartment listings with their prices and their addresses.",
+ z.object({
+ listings: z.array(
+ z.object({
+ price: z.string().describe("The price of the listing"),
+ address: z.string().describe("The address of the listing"),
+ }),
+ ),
+ }),
+ );
+
+ const listings = apartment_listings.listings;
+ console.log(listings);
+ console.log(`found ${listings.length} listings`);
+}
+
+(async () => {
+ const v3 = new V3({
+ env: "LOCAL",
+ verbose: 2,
+ logInferenceToFile: false,
+ model: "google/gemini-2.0-flash",
+ cacheDir: "stagehand-extract-cache",
+ });
+ await v3.init();
+ await example(v3);
+})();
diff --git a/packages/core/examples/wordle.ts b/packages/core/examples/wordle.ts
new file mode 100644
index 000000000..5d3189a61
--- /dev/null
+++ b/packages/core/examples/wordle.ts
@@ -0,0 +1,24 @@
+import { Stagehand } from "../lib/v3";
+
+async function example() {
+ const stagehand = new Stagehand({
+ env: "BROWSERBASE",
+ verbose: 1,
+ });
+ await stagehand.init();
+ const page = stagehand.context.pages()[0];
+ await page.goto("https://www.nytimes.com/games/wordle/index.html");
+ await stagehand.act("click 'Continue'");
+ await stagehand.act("click 'Play'");
+ await stagehand.act("click cross sign on top right of 'How To Play' card");
+ const word = "WORDS";
+ for (const letter of word) {
+ await stagehand.act(`press ${letter}`);
+ }
+ await stagehand.act("press enter");
+ await stagehand.close();
+}
+
+(async () => {
+ await example();
+})();
diff --git a/packages/core/lib/CHANGELOG.md b/packages/core/lib/CHANGELOG.md
new file mode 100644
index 000000000..6b0ab62a9
--- /dev/null
+++ b/packages/core/lib/CHANGELOG.md
@@ -0,0 +1,63 @@
+# @browserbasehq/stagehand-lib
+
+## 2.4.1
+
+### Patch Changes
+
+- [#1027](https://github.com/browserbase/stagehand/pull/1027) [`455b61f`](https://github.com/browserbase/stagehand/commit/455b61fb6f7a34ae50d7e7c76c1d639241e213d6) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Fixed small issue with module-level state guard for the Playwright selectors.register call
+
+## 2.4.0
+
+### Minor Changes
+
+- [#778](https://github.com/browserbase/stagehand/pull/778) [`df570b6`](https://github.com/browserbase/stagehand/commit/df570b67e46febcaf7282ffb65dd5707e2808152) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - iframe support
+
+### Patch Changes
+
+- [#809](https://github.com/browserbase/stagehand/pull/809) [`03ebebc`](https://github.com/browserbase/stagehand/commit/03ebebc0317f92d8de77285cc2e66dc0131fe9fe) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - log NoObjectGenerated error details
+
+- [#801](https://github.com/browserbase/stagehand/pull/801) [`1d4f0ab`](https://github.com/browserbase/stagehand/commit/1d4f0abca47bf47ae8b7aeb53f3cd1155a7e5448) Thanks [@miguelg719](https://github.com/miguelg719)! - Default use API to true
+
+- [#798](https://github.com/browserbase/stagehand/pull/798) [`d86200b`](https://github.com/browserbase/stagehand/commit/d86200bd5bde4c5ba113ca89e28ab86c14a8304e) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix pino logging memory leak by reusing worker
+
+## 2.3.0
+
+### Minor Changes
+
+- [#731](https://github.com/browserbase/stagehand/pull/731) [`393c8e0`](https://github.com/browserbase/stagehand/commit/393c8e05d016086e481c0043ee6b084c61886cad) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - make extract() with no arguments return the hybrid tree instead of text-rendered webpage
+
+- [#737](https://github.com/browserbase/stagehand/pull/737) [`6ef6073`](https://github.com/browserbase/stagehand/commit/6ef60730cab0ad9025f44b6eeb2c83751d1dcd35) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - deprecate useTextExtract and remove functionality
+
+### Patch Changes
+
+- [#741](https://github.com/browserbase/stagehand/pull/741) [`5680d25`](https://github.com/browserbase/stagehand/commit/5680d2509352c383ad502c9f4fabde01fa638833) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - use safeparse for zod validation
+
+- [#740](https://github.com/browserbase/stagehand/pull/740) [`28840a7`](https://github.com/browserbase/stagehand/commit/28840a7d3fec89a490984582fb37fa3d007c0349) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - dont log deprecation warning when onlyVisible is undefined
+
+- [#755](https://github.com/browserbase/stagehand/pull/755) [`ba687ab`](https://github.com/browserbase/stagehand/commit/ba687abdfb598f839ddfec0442d3d7b6b696b0a3) Thanks [@miguelg719](https://github.com/miguelg719)! - Fix context init error on undefined context
+
+- [#789](https://github.com/browserbase/stagehand/pull/789) [`c5ff8ce`](https://github.com/browserbase/stagehand/commit/c5ff8ce2d7467b70a450ca52bc3e03b15280ce1b) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix noisy useTextExtract deprecation log
+
+- [#757](https://github.com/browserbase/stagehand/pull/757) [`628e534`](https://github.com/browserbase/stagehand/commit/628e534ea6d7ca081bad6c32167c7d53d4772eed) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - optimize CDP calls when building hybrid tree
+
+- [#772](https://github.com/browserbase/stagehand/pull/772) [`64d331d`](https://github.com/browserbase/stagehand/commit/64d331dc2eba86675a8b148d361897f55f170703) Thanks [@miguelg719](https://github.com/miguelg719)! - Fixes an issue with the new tab intercepts for invalid urls
+
+- [#770](https://github.com/browserbase/stagehand/pull/770) [`d312a43`](https://github.com/browserbase/stagehand/commit/d312a43672fe2865abcf184a712a759a12f5b9d1) Thanks [@miguelg719](https://github.com/miguelg719)! - Removed default chromium flags that delay browser launching
+
+- [#753](https://github.com/browserbase/stagehand/pull/753) [`fbca400`](https://github.com/browserbase/stagehand/commit/fbca4003a547dc5eee0c0be5edc5e98c1f4d8c22) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix `stagehand.history`
+
+- [#745](https://github.com/browserbase/stagehand/pull/745) [`c54afab`](https://github.com/browserbase/stagehand/commit/c54afab0e43a2144eecbc56df7f33c5e444ceed5) Thanks [@miguelg719](https://github.com/miguelg719)! - Add an identifier for client language/runtime
+
+- [#768](https://github.com/browserbase/stagehand/pull/768) [`58b06eb`](https://github.com/browserbase/stagehand/commit/58b06eb2fdfb1a9cd84c03f46655ab0ea00ee07f) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: page.evaluate: Execution context was destroyed, most likely because of a navigation
+
+- [#758](https://github.com/browserbase/stagehand/pull/758) [`98e1356`](https://github.com/browserbase/stagehand/commit/98e13566846a547003e4c9aebbe4f95eff653bba) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - rm unused functions
+
+- [#781](https://github.com/browserbase/stagehand/pull/781) [`8d239ce`](https://github.com/browserbase/stagehand/commit/8d239cec7a835d35243b2b00c3c00c1b66c05b5e) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix variable parsing issue with gpt-4.1
+
+- [#761](https://github.com/browserbase/stagehand/pull/761) [`e1f7074`](https://github.com/browserbase/stagehand/commit/e1f7074be23c82ae897386d5e5e132ff8cb4120a) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - build xpaths on node side instead of using injected JS
+
+## 2.2.1
+
+### Patch Changes
+
+- [#729](https://github.com/browserbase/stagehand/pull/729) [`fc24f84`](https://github.com/browserbase/stagehand/commit/fc24f848ee0f300182e88993dfe8d68025d69fcb) Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix "failed to inject helper scripts" log on stagehand.close()
diff --git a/lib/inference.ts b/packages/core/lib/inference.ts
similarity index 51%
rename from lib/inference.ts
rename to packages/core/lib/inference.ts
index 82565ceaf..9b843e043 100644
--- a/lib/inference.ts
+++ b/packages/core/lib/inference.ts
@@ -1,74 +1,34 @@
import { z } from "zod";
-import { LogLine } from "../types/log";
-import { ChatMessage, LLMClient } from "./llm/LLMClient";
+import { LogLine } from "./v3/types/public/logs";
+import { ChatMessage, LLMClient } from "./v3/llm/LLMClient";
import {
+ buildActSystemPrompt,
buildExtractSystemPrompt,
buildExtractUserPrompt,
buildMetadataPrompt,
buildMetadataSystemPrompt,
buildObserveSystemPrompt,
buildObserveUserMessage,
- buildRefineSystemPrompt,
- buildRefineUserPrompt,
} from "./prompt";
-import {
- appendSummary,
- writeTimestampedTxtFile,
-} from "@/lib/inferenceLogUtils";
-
-/**
- * Replaces <|VARIABLE|> placeholders in a text with user-provided values.
- */
-export function fillInVariables(
- text: string,
- variables: Record,
-) {
- let processedText = text;
- Object.entries(variables).forEach(([key, value]) => {
- const placeholder = `<|${key.toUpperCase()}|>`;
- processedText = processedText.replace(placeholder, value);
- });
- return processedText;
-}
+import { appendSummary, writeTimestampedTxtFile } from "./inferenceLogUtils";
+import type { InferStagehandSchema, StagehandZodObject } from "./v3/zodCompat";
-/** Simple usage shape if your LLM returns usage tokens. */
-interface LLMUsage {
- prompt_tokens: number;
- completion_tokens: number;
- total_tokens: number;
-}
+// Re-export for backward compatibility
+export type { LLMParsedResponse, LLMUsage } from "./v3/llm/LLMClient";
-/**
- * For calls that use a schema: the LLMClient may return { data: T; usage?: LLMUsage }
- */
-export interface LLMParsedResponse {
- data: T;
- usage?: LLMUsage;
-}
-
-export async function extract({
+export async function extract({
instruction,
- previouslyExtractedContent,
domElements,
schema,
llmClient,
- chunksSeen,
- chunksTotal,
- requestId,
logger,
- isUsingTextExtract,
userProvidedInstructions,
logInferenceToFile = false,
}: {
instruction: string;
- previouslyExtractedContent: object;
domElements: string;
- schema: z.ZodObject;
+ schema: T;
llmClient: LLMClient;
- chunksSeen: number;
- chunksTotal: number;
- requestId: string;
- isUsingTextExtract?: boolean;
userProvidedInstructions?: string;
logger: (message: LogLine) => void;
logInferenceToFile?: boolean;
@@ -86,17 +46,14 @@ export async function extract({
),
});
- type ExtractionResponse = z.infer;
+ type ExtractionResponse = InferStagehandSchema;
type MetadataResponse = z.infer;
const isUsingAnthropic = llmClient.type === "anthropic";
+ const isGPT5 = llmClient.modelName.includes("gpt-5"); // TODO: remove this as we update support for gpt-5 configuration options
const extractCallMessages: ChatMessage[] = [
- buildExtractSystemPrompt(
- isUsingAnthropic,
- isUsingTextExtract,
- userProvidedInstructions,
- ),
+ buildExtractSystemPrompt(isUsingAnthropic, userProvidedInstructions),
buildExtractUserPrompt(instruction, domElements, isUsingAnthropic),
];
@@ -107,7 +64,6 @@ export async function extract({
"extract_summary",
"extract_call",
{
- requestId,
modelCall: "extract",
messages: extractCallMessages,
},
@@ -125,18 +81,16 @@ export async function extract({
schema,
name: "Extraction",
},
- temperature: 0.1,
+ temperature: isGPT5 ? 1 : 0.1,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0,
- requestId,
},
logger,
});
const extractEndTime = Date.now();
- const { data: extractedData, usage: extractUsage } =
- extractionResponse as LLMParsedResponse;
+ const { data: extractedData, usage: extractUsage } = extractionResponse;
let extractResponseFile = "";
if (logInferenceToFile) {
@@ -144,7 +98,6 @@ export async function extract({
"extract_summary",
"extract_response",
{
- requestId,
modelResponse: "extract",
rawResponse: extractedData,
},
@@ -158,89 +111,15 @@ export async function extract({
LLM_output_file: extractResponseFile,
prompt_tokens: extractUsage?.prompt_tokens ?? 0,
completion_tokens: extractUsage?.completion_tokens ?? 0,
+ reasoning_tokens: extractUsage?.reasoning_tokens ?? 0,
+ cached_input_tokens: extractUsage?.cached_input_tokens ?? 0,
inference_time_ms: extractEndTime - extractStartTime,
});
}
- const refineCallMessages: ChatMessage[] = [
- buildRefineSystemPrompt(),
- buildRefineUserPrompt(
- instruction,
- previouslyExtractedContent,
- extractedData,
- ),
- ];
-
- let refineCallFile = "";
- let refineCallTimestamp = "";
- if (logInferenceToFile) {
- const { fileName, timestamp } = writeTimestampedTxtFile(
- "extract_summary",
- "refine_call",
- {
- requestId,
- modelCall: "refine",
- messages: refineCallMessages,
- },
- );
- refineCallFile = fileName;
- refineCallTimestamp = timestamp;
- }
-
- const refineStartTime = Date.now();
- const refinedResponse =
- await llmClient.createChatCompletion({
- options: {
- messages: refineCallMessages,
- response_model: {
- schema,
- name: "RefinedExtraction",
- },
- temperature: 0.1,
- top_p: 1,
- frequency_penalty: 0,
- presence_penalty: 0,
- requestId,
- },
- logger,
- });
- const refineEndTime = Date.now();
-
- const { data: refinedResponseData, usage: refinedResponseUsage } =
- refinedResponse as LLMParsedResponse;
-
- let refineResponseFile = "";
- if (logInferenceToFile) {
- const { fileName } = writeTimestampedTxtFile(
- "extract_summary",
- "refine_response",
- {
- requestId,
- modelResponse: "refine",
- rawResponse: refinedResponseData,
- },
- );
- refineResponseFile = fileName;
-
- appendSummary("extract", {
- extract_inference_type: "refine",
- timestamp: refineCallTimestamp,
- LLM_input_file: refineCallFile,
- LLM_output_file: refineResponseFile,
- prompt_tokens: refinedResponseUsage?.prompt_tokens ?? 0,
- completion_tokens: refinedResponseUsage?.completion_tokens ?? 0,
- inference_time_ms: refineEndTime - refineStartTime,
- });
- }
-
const metadataCallMessages: ChatMessage[] = [
buildMetadataSystemPrompt(),
- buildMetadataPrompt(
- instruction,
- refinedResponseData,
- chunksSeen,
- chunksTotal,
- ),
+ buildMetadataPrompt(instruction, extractedData),
];
let metadataCallFile = "";
@@ -250,7 +129,6 @@ export async function extract({
"extract_summary",
"metadata_call",
{
- requestId,
modelCall: "metadata",
messages: metadataCallMessages,
},
@@ -268,11 +146,10 @@ export async function extract({
name: "Metadata",
schema: metadataSchema,
},
- temperature: 0.1,
+ temperature: isGPT5 ? 1 : 0.1,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0,
- requestId,
},
logger,
});
@@ -284,7 +161,7 @@ export async function extract({
progress: metadataResponseProgress,
},
usage: metadataResponseUsage,
- } = metadataResponse as LLMParsedResponse;
+ } = metadataResponse;
let metadataResponseFile = "";
if (logInferenceToFile) {
@@ -292,7 +169,6 @@ export async function extract({
"extract_summary",
"metadata_response",
{
- requestId,
modelResponse: "metadata",
completed: metadataResponseCompleted,
progress: metadataResponseProgress,
@@ -307,34 +183,39 @@ export async function extract({
LLM_output_file: metadataResponseFile,
prompt_tokens: metadataResponseUsage?.prompt_tokens ?? 0,
completion_tokens: metadataResponseUsage?.completion_tokens ?? 0,
+ reasoning_tokens: metadataResponseUsage?.reasoning_tokens ?? 0,
+ cached_input_tokens: metadataResponseUsage?.cached_input_tokens ?? 0,
inference_time_ms: metadataEndTime - metadataStartTime,
});
}
const totalPromptTokens =
(extractUsage?.prompt_tokens ?? 0) +
- (refinedResponseUsage?.prompt_tokens ?? 0) +
(metadataResponseUsage?.prompt_tokens ?? 0);
const totalCompletionTokens =
(extractUsage?.completion_tokens ?? 0) +
- (refinedResponseUsage?.completion_tokens ?? 0) +
(metadataResponseUsage?.completion_tokens ?? 0);
const totalInferenceTimeMs =
- extractEndTime -
- extractStartTime +
- (refineEndTime - refineStartTime) +
- (metadataEndTime - metadataStartTime);
+ extractEndTime - extractStartTime + (metadataEndTime - metadataStartTime);
+ const totalReasoningTokens =
+ (extractUsage?.reasoning_tokens ?? 0) +
+ (metadataResponseUsage?.reasoning_tokens ?? 0);
+ const totalCachedInputTokens =
+ (extractUsage?.cached_input_tokens ?? 0) +
+ (metadataResponseUsage?.cached_input_tokens ?? 0);
return {
- ...refinedResponseData,
+ ...extractedData,
metadata: {
completed: metadataResponseCompleted,
progress: metadataResponseProgress,
},
prompt_tokens: totalPromptTokens,
completion_tokens: totalCompletionTokens,
+ reasoning_tokens: totalReasoningTokens,
+ cached_input_tokens: totalCachedInputTokens,
inference_time_ms: totalInferenceTimeMs,
};
}
@@ -343,82 +224,65 @@ export async function observe({
instruction,
domElements,
llmClient,
- requestId,
- isUsingAccessibilityTree,
userProvidedInstructions,
logger,
- returnAction = false,
logInferenceToFile = false,
- fromAct,
}: {
instruction: string;
domElements: string;
llmClient: LLMClient;
- requestId: string;
userProvidedInstructions?: string;
logger: (message: LogLine) => void;
- isUsingAccessibilityTree?: boolean;
- returnAction?: boolean;
logInferenceToFile?: boolean;
- fromAct?: boolean;
}) {
+ const isGPT5 = llmClient.modelName.includes("gpt-5"); // TODO: remove this as we update support for gpt-5 configuration options
+
const observeSchema = z.object({
elements: z
.array(
z.object({
- elementId: z.number().describe("the number of the element"),
+ elementId: z
+ .string()
+ .describe(
+ "the ID string associated with the element. Never include surrounding square brackets. This field must follow the format of 'number-number'.",
+ ),
description: z
.string()
.describe(
- isUsingAccessibilityTree
- ? "a description of the accessible element and its purpose"
- : "a description of the element and what it is relevant for",
+ "a description of the accessible element and its purpose",
),
- ...(returnAction
- ? {
- method: z
- .string()
- .describe(
- "the candidate method/action to interact with the element. Select one of the available Playwright interaction methods.",
- ),
- arguments: z.array(
- z
- .string()
- .describe(
- "the arguments to pass to the method. For example, for a click, the arguments are empty, but for a fill, the arguments are the value to fill in.",
- ),
- ),
- }
- : {}),
+ method: z
+ .string()
+ .describe(
+ "the candidate method/action to interact with the element. Select one of the available Playwright interaction methods.",
+ ),
+ arguments: z.array(
+ z
+ .string()
+ .describe(
+ "the arguments to pass to the method. For example, for a click, the arguments are empty, but for a fill, the arguments are the value to fill in.",
+ ),
+ ),
}),
)
- .describe(
- isUsingAccessibilityTree
- ? "an array of accessible elements that match the instruction"
- : "an array of elements that match the instruction",
- ),
+ .describe("an array of accessible elements that match the instruction"),
});
type ObserveResponse = z.infer;
const messages: ChatMessage[] = [
- buildObserveSystemPrompt(
- userProvidedInstructions,
- isUsingAccessibilityTree,
- ),
- buildObserveUserMessage(instruction, domElements, isUsingAccessibilityTree),
+ buildObserveSystemPrompt(userProvidedInstructions),
+ buildObserveUserMessage(instruction, domElements),
];
- const filePrefix = fromAct ? "act" : "observe";
let callTimestamp = "";
let callFile = "";
if (logInferenceToFile) {
const { fileName, timestamp } = writeTimestampedTxtFile(
- `${filePrefix}_summary`,
- `${filePrefix}_call`,
+ `observe_summary`,
+ `observe_call`,
{
- requestId,
- modelCall: filePrefix,
+ modelCall: "observe",
messages,
},
);
@@ -434,42 +298,43 @@ export async function observe({
schema: observeSchema,
name: "Observation",
},
- temperature: 0.1,
+ temperature: isGPT5 ? 1 : 0.1,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0,
- requestId,
},
logger,
});
const end = Date.now();
const usageTimeMs = end - start;
- const { data: observeData, usage: observeUsage } =
- rawResponse as LLMParsedResponse;
+ const { data: observeData, usage: observeUsage } = rawResponse;
const promptTokens = observeUsage?.prompt_tokens ?? 0;
const completionTokens = observeUsage?.completion_tokens ?? 0;
+ const reasoningTokens = observeUsage?.reasoning_tokens ?? 0;
+ const cachedInputTokens = observeUsage?.cached_input_tokens ?? 0;
let responseFile = "";
if (logInferenceToFile) {
const { fileName: responseFileName } = writeTimestampedTxtFile(
- `${filePrefix}_summary`,
- `${filePrefix}_response`,
+ `observe_summary`,
+ `observe_response`,
{
- requestId,
- modelResponse: filePrefix,
+ modelResponse: "observe",
rawResponse: observeData,
},
);
responseFile = responseFileName;
- appendSummary(filePrefix, {
- [`${filePrefix}_inference_type`]: filePrefix,
+ appendSummary("observe", {
+ [`observe_inference_type`]: "observe",
timestamp: callTimestamp,
LLM_input_file: callFile,
LLM_output_file: responseFile,
prompt_tokens: promptTokens,
completion_tokens: completionTokens,
+ reasoning_tokens: reasoningTokens,
+ cached_input_tokens: cachedInputTokens,
inference_time_ms: usageTimeMs,
});
}
@@ -477,16 +342,11 @@ export async function observe({
const parsedElements =
observeData.elements?.map((el) => {
const base = {
- elementId: Number(el.elementId),
+ elementId: el.elementId,
description: String(el.description),
+ method: String(el.method),
+ arguments: el.arguments,
};
- if (returnAction) {
- return {
- ...base,
- method: String(el.method),
- arguments: el.arguments,
- };
- }
return base;
}) ?? [];
@@ -494,6 +354,138 @@ export async function observe({
elements: parsedElements,
prompt_tokens: promptTokens,
completion_tokens: completionTokens,
+ reasoning_tokens: reasoningTokens,
+ cached_input_tokens: cachedInputTokens,
+ inference_time_ms: usageTimeMs,
+ };
+}
+
+export async function act({
+ instruction,
+ domElements,
+ llmClient,
+ userProvidedInstructions,
+ logger,
+ logInferenceToFile = false,
+}: {
+ instruction: string;
+ domElements: string;
+ llmClient: LLMClient;
+ userProvidedInstructions?: string;
+ logger: (message: LogLine) => void;
+ logInferenceToFile?: boolean;
+}) {
+ const isGPT5 = llmClient.modelName.includes("gpt-5"); // TODO: remove this as we update support for gpt-5 configuration options
+
+ const actSchema = z.object({
+ elementId: z
+ .string()
+ .describe(
+ "the ID string associated with the element. Never include surrounding square brackets. This field must follow the format of 'number-number'.",
+ ),
+ description: z
+ .string()
+ .describe("a description of the accessible element and its purpose"),
+ method: z
+ .string()
+ .describe(
+ "the candidate method/action to interact with the element. Select one of the available Playwright interaction methods.",
+ ),
+ arguments: z.array(
+ z
+ .string()
+ .describe(
+ "the arguments to pass to the method. For example, for a click, the arguments are empty, but for a fill, the arguments are the value to fill in.",
+ ),
+ ),
+ twoStep: z.boolean(),
+ });
+
+ type ActResponse = z.infer;
+
+ const messages: ChatMessage[] = [
+ buildActSystemPrompt(userProvidedInstructions),
+ buildObserveUserMessage(instruction, domElements),
+ ];
+
+ let callTimestamp = "";
+ let callFile = "";
+ if (logInferenceToFile) {
+ const { fileName, timestamp } = writeTimestampedTxtFile(
+ `act_summary`,
+ `act_call`,
+ {
+ modelCall: "act",
+ messages,
+ },
+ );
+ callFile = fileName;
+ callTimestamp = timestamp;
+ }
+
+ const start = Date.now();
+ const rawResponse = await llmClient.createChatCompletion({
+ options: {
+ messages,
+ response_model: {
+ schema: actSchema,
+ name: "act",
+ },
+ temperature: isGPT5 ? 1 : 0.1,
+ top_p: 1,
+ frequency_penalty: 0,
+ presence_penalty: 0,
+ },
+ logger,
+ });
+ const end = Date.now();
+ const usageTimeMs = end - start;
+
+ const { data: actData, usage: actUsage } = rawResponse;
+ const promptTokens = actUsage?.prompt_tokens ?? 0;
+ const completionTokens = actUsage?.completion_tokens ?? 0;
+ const reasoningTokens = actUsage?.reasoning_tokens ?? 0;
+ const cachedInputTokens = actUsage?.cached_input_tokens ?? 0;
+
+ let responseFile = "";
+ if (logInferenceToFile) {
+ const { fileName: responseFileName } = writeTimestampedTxtFile(
+ `act_summary`,
+ `act_response`,
+ {
+ modelResponse: "act",
+ rawResponse: actData,
+ },
+ );
+ responseFile = responseFileName;
+
+ appendSummary("act", {
+ [`act_inference_type`]: "act",
+ timestamp: callTimestamp,
+ LLM_input_file: callFile,
+ LLM_output_file: responseFile,
+ prompt_tokens: promptTokens,
+ completion_tokens: completionTokens,
+ reasoning_tokens: reasoningTokens,
+ cached_input_tokens: cachedInputTokens,
+ inference_time_ms: usageTimeMs,
+ });
+ }
+
+ const parsedElement = {
+ elementId: actData.elementId,
+ description: String(actData.description),
+ method: String(actData.method),
+ arguments: actData.arguments,
+ };
+
+ return {
+ element: parsedElement,
+ prompt_tokens: promptTokens,
+ completion_tokens: completionTokens,
+ reasoning_tokens: reasoningTokens,
+ cached_input_tokens: cachedInputTokens,
inference_time_ms: usageTimeMs,
+ twoStep: actData.twoStep,
};
}
diff --git a/lib/inferenceLogUtils.ts b/packages/core/lib/inferenceLogUtils.ts
similarity index 100%
rename from lib/inferenceLogUtils.ts
rename to packages/core/lib/inferenceLogUtils.ts
index 4a5f8693b..a6a60ca61 100644
--- a/lib/inferenceLogUtils.ts
+++ b/packages/core/lib/inferenceLogUtils.ts
@@ -1,5 +1,5 @@
-import path from "path";
import fs from "fs";
+import path from "path";
/**
* Create (or ensure) a parent directory named "inference_summary".
diff --git a/lib/logger.ts b/packages/core/lib/logger.ts
similarity index 58%
rename from lib/logger.ts
rename to packages/core/lib/logger.ts
index 54814e434..fb755a749 100644
--- a/lib/logger.ts
+++ b/packages/core/lib/logger.ts
@@ -1,5 +1,5 @@
import pino from "pino";
-import { LogLine } from "../types/log";
+import { LogLine } from "./v3/types/public/logs";
// Map our existing levels to Pino's standard levels
const levelMapping: Record = {
@@ -71,9 +71,33 @@ function isTestEnvironment(): boolean {
/**
* StagehandLogger class that wraps Pino for our specific needs
+ *
+ * LOGGING PRECEDENCE:
+ *
+ * Test environments:
+ * - External logger provided -> external logger only.
+ * - No external logger -> console fallback only (Pino disabled).
+ *
+ * Non-test environments:
+ * - usePino === true -> emit via Pino and also call the external logger when present.
+ * - usePino === false -> disable Pino; use the external logger when present, otherwise console fallback.
+ * - usePino === undefined -> prefer the external logger when present; otherwise use Pino.
+ *
+ * SHARED PINO OPTIMIZATION:
+ * We maintain a single shared Pino instance when `usePino` is enabled.
+ * This prevents spawning a new worker thread for every Stagehand instance
+ * (which happens when `pino-pretty` transport is used), eliminating the
+ * memory/RSS growth observed when many Stagehand objects are created and
+ * disposed within the same process (e.g. a request-per-instance API).
*/
export class StagehandLogger {
- private logger: pino.Logger;
+ /**
+ * Shared Pino logger instance across all StagehandLogger instances.
+ * First instance to enable Pino creates it, subsequent instances reuse it.
+ */
+ private static sharedPinoLogger: pino.Logger | null = null;
+
+ private logger?: pino.Logger;
private verbose: 0 | 1 | 2;
private externalLogger?: (logLine: LogLine) => void;
private usePino: boolean;
@@ -84,13 +108,30 @@ export class StagehandLogger {
externalLogger?: (logLine: LogLine) => void,
) {
this.isTest = isTestEnvironment();
+ this.externalLogger = externalLogger;
- // In test environments, default to not using Pino to avoid worker thread issues
- this.usePino = this.isTest ? false : options.usePino !== false; // Default to using Pino if not specified and not in test
+ const externalProvided = typeof externalLogger === "function";
+ const explicitUsePino = options.usePino;
+
+ if (this.isTest) {
+ this.usePino = false;
+ } else if (explicitUsePino === true) {
+ this.usePino = true;
+ } else if (explicitUsePino === false) {
+ this.usePino = false;
+ } else {
+ this.usePino = !externalProvided;
+ }
+
+ if (this.usePino) {
+ // Re-use (or create) a single shared Pino logger instance
+ if (!StagehandLogger.sharedPinoLogger) {
+ StagehandLogger.sharedPinoLogger = createLogger(options);
+ }
+ this.logger = StagehandLogger.sharedPinoLogger;
+ }
- this.logger = this.usePino ? createLogger(options) : null;
this.verbose = 1; // Default verbosity level
- this.externalLogger = externalLogger;
}
/**
@@ -124,23 +165,52 @@ export class StagehandLogger {
return;
}
- // If we're in a test environment but no external logger is provided,
- // use console for basic logging
- if (this.isTest && !this.externalLogger) {
+ // For test environments WITHOUT an external logger OR for cases where Pino
+ // is disabled and no external logger is provided, fall back to console.* so
+ // users still see logs (non-colourised).
+ const shouldFallbackToConsole =
+ (!this.usePino && !this.externalLogger) ||
+ (this.isTest && !this.externalLogger);
+
+ if (shouldFallbackToConsole) {
const level = logLine.level ?? 1;
- const prefix = `[${logLine.category || "log"}] `;
+ const ts = logLine.timestamp ?? new Date().toISOString();
+ const levelStr = level === 0 ? "ERROR" : level === 2 ? "DEBUG" : "INFO";
+
+ // Format like Pino: [timestamp] LEVEL: message
+ let output = `[${ts}] ${levelStr}: ${logLine.message}`;
+
+ // Add auxiliary data on separate indented lines (like Pino pretty format)
+ if (logLine.auxiliary) {
+ const formattedData = this.formatAuxiliaryData(logLine.auxiliary);
+ for (const [key, value] of Object.entries(formattedData)) {
+ let formattedValue: string;
+ if (typeof value === "object" && value !== null) {
+ // Pretty print objects with indentation
+ formattedValue = JSON.stringify(value, null, 2)
+ .split("\n")
+ .map((line, i) => (i === 0 ? line : ` ${line}`))
+ .join("\n");
+ } else {
+ formattedValue = String(value);
+ }
+ output += `\n ${key}: ${formattedValue}`;
+ }
+ }
switch (level) {
case 0:
- console.error(prefix + logLine.message);
+ console.error(output);
break;
case 1:
- console.log(prefix + logLine.message);
+ console.log(output);
break;
case 2:
- console.debug(prefix + logLine.message);
+ console.debug(output);
break;
}
+
+ return; // already handled via console output, avoid duplicate logging
}
if (this.usePino && this.logger) {
@@ -170,8 +240,11 @@ export class StagehandLogger {
}
}
- // Use external logger if provided and either Pino is disabled or we're in a test
- if (this.externalLogger && (!this.usePino || this.isTest)) {
+ // IMPORTANT: External logger receives logs ALWAYS when provided (takes precedence)
+ // This ensures user-provided loggers (e.g., EvalLogger, custom loggers) capture all logs
+ // regardless of Pino configuration. Pino is used for console output, external logger
+ // is used for programmatic log capture.
+ if (this.externalLogger) {
this.externalLogger(logLine);
}
}
@@ -185,27 +258,40 @@ export class StagehandLogger {
const formattedData: Record = {};
for (const [key, { value, type }] of Object.entries(auxiliary)) {
+ let formattedValue: unknown;
+
// Convert values based on their type
switch (type) {
case "integer":
- formattedData[key] = parseInt(value, 10);
+ formattedValue = parseInt(value, 10);
break;
case "float":
- formattedData[key] = parseFloat(value);
+ formattedValue = parseFloat(value);
break;
case "boolean":
- formattedData[key] = value === "true";
+ formattedValue = value === "true";
break;
case "object":
try {
- formattedData[key] = JSON.parse(value);
+ formattedValue = JSON.parse(value);
} catch {
- formattedData[key] = value;
+ formattedValue = value;
}
break;
default:
- formattedData[key] = value;
+ formattedValue = value;
}
+
+ // Skip undefined values and empty objects/arrays
+ if (formattedValue === undefined) continue;
+ if (typeof formattedValue === "object" && formattedValue !== null) {
+ const isEmpty = Array.isArray(formattedValue)
+ ? formattedValue.length === 0
+ : Object.keys(formattedValue).length === 0;
+ if (isEmpty) continue;
+ }
+
+ formattedData[key] = formattedValue;
}
return formattedData;
@@ -225,7 +311,7 @@ export class StagehandLogger {
warn(message: string, data?: Record): void {
this.log({
message,
- level: 0, // Using error level for warnings as we don't have a separate warning level
+ level: 1,
category: "warning",
auxiliary: this.convertToAuxiliary(data),
});
diff --git a/packages/core/lib/modelUtils.ts b/packages/core/lib/modelUtils.ts
new file mode 100644
index 000000000..ec8d69689
--- /dev/null
+++ b/packages/core/lib/modelUtils.ts
@@ -0,0 +1,62 @@
+import { ClientOptions, ModelConfiguration } from "./v3/types/public/model";
+import {
+ AVAILABLE_CUA_MODELS,
+ AvailableCuaModel,
+} from "./v3/types/public/agent";
+
+export function splitModelName(model: string): {
+ provider: string;
+ modelName: string;
+} {
+ const firstSlashIndex = model.indexOf("/");
+ const provider = model.substring(0, firstSlashIndex);
+ const modelName = model.substring(firstSlashIndex + 1);
+ return { provider, modelName };
+}
+
+export function resolveModel(model: string | ModelConfiguration): {
+ provider: string;
+ modelName: string;
+ clientOptions: ClientOptions;
+ isCua: boolean;
+} {
+ // Extract the model string and client options
+ const modelString = typeof model === "string" ? model : model.modelName;
+ const clientOptions =
+ typeof model === "string"
+ ? {}
+ : (() => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { modelName: _, ...rest } = model;
+ return rest;
+ })();
+
+ // Check if provider is explicitly set in clientOptions
+ const hasExplicitProvider = clientOptions.provider !== undefined;
+
+ // If provider is explicitly set, don't split the model name - pass it through as-is
+ let provider: string;
+ let parsedModelName: string;
+
+ if (hasExplicitProvider) {
+ provider = clientOptions.provider as string;
+ parsedModelName = modelString; // Keep the full model name
+ } else {
+ // Parse the model string normally
+ const split = splitModelName(modelString);
+ provider = split.provider;
+ parsedModelName = split.modelName;
+ }
+
+ // Check if it's a CUA model
+ const isCua =
+ hasExplicitProvider ||
+ AVAILABLE_CUA_MODELS.includes(modelString as AvailableCuaModel);
+
+ return {
+ provider,
+ modelName: parsedModelName,
+ clientOptions,
+ isCua,
+ };
+}
diff --git a/packages/core/lib/prompt.ts b/packages/core/lib/prompt.ts
new file mode 100644
index 000000000..de8806af2
--- /dev/null
+++ b/packages/core/lib/prompt.ts
@@ -0,0 +1,289 @@
+import { ChatMessage } from "./v3/llm/LLMClient";
+
+export function buildUserInstructionsString(
+ userProvidedInstructions?: string,
+): string {
+ if (!userProvidedInstructions) {
+ return "";
+ }
+
+ return `\n\n# Custom Instructions Provided by the User
+
+Please keep the user's instructions in mind when performing actions. If the user's instructions are not relevant to the current task, ignore them.
+
+User Instructions:
+${userProvidedInstructions}`;
+}
+
+// extract
+export function buildExtractSystemPrompt(
+ isUsingPrintExtractedDataTool: boolean = false,
+ userProvidedInstructions?: string,
+): ChatMessage {
+ const baseContent = `You are extracting content on behalf of a user.
+ If a user asks you to extract a 'list' of information, or 'all' information,
+ YOU MUST EXTRACT ALL OF THE INFORMATION THAT THE USER REQUESTS.
+
+ You will be given:
+1. An instruction
+2. `;
+
+ const contentDetail = `A list of DOM elements to extract from.`;
+
+ const instructions = `
+Print the exact text from the DOM elements with all symbols, characters, and endlines as is.
+Print null or an empty string if no new information is found.
+ `.trim();
+
+ const toolInstructions = isUsingPrintExtractedDataTool
+ ? `
+ONLY print the content using the print_extracted_data tool provided.
+ONLY print the content using the print_extracted_data tool provided.
+ `.trim()
+ : "";
+
+ const additionalInstructions =
+ "If a user is attempting to extract links or URLs, you MUST respond with ONLY the IDs of the link elements. \n" +
+ "Do not attempt to extract links directly from the text unless absolutely necessary. ";
+
+ const userInstructions = buildUserInstructionsString(
+ userProvidedInstructions,
+ );
+
+ const content =
+ `${baseContent}${contentDetail}\n\n${instructions}\n${toolInstructions}${
+ additionalInstructions ? `\n\n${additionalInstructions}` : ""
+ }${userInstructions ? `\n\n${userInstructions}` : ""}`.replace(/\s+/g, " ");
+
+ return {
+ role: "system",
+ content,
+ };
+}
+
+export function buildExtractUserPrompt(
+ instruction: string,
+ domElements: string,
+ isUsingPrintExtractedDataTool: boolean = false,
+): ChatMessage {
+ let content = `Instruction: ${instruction}
+DOM: ${domElements}`;
+
+ if (isUsingPrintExtractedDataTool) {
+ content += `
+ONLY print the content using the print_extracted_data tool provided.
+ONLY print the content using the print_extracted_data tool provided.`;
+ }
+
+ return {
+ role: "user",
+ content,
+ };
+}
+
+const metadataSystemPrompt = `You are an AI assistant tasked with evaluating the progress and completion status of an extraction task.
+Analyze the extraction response and determine if the task is completed or if more information is needed.
+Strictly abide by the following criteria:
+1. Once the instruction has been satisfied by the current extraction response, ALWAYS set completion status to true and stop processing, regardless of remaining chunks.
+2. Only set completion status to false if BOTH of these conditions are true:
+ - The instruction has not been satisfied yet
+ - There are still chunks left to process (chunksTotal > chunksSeen)`;
+
+export function buildMetadataSystemPrompt(): ChatMessage {
+ return {
+ role: "system",
+ content: metadataSystemPrompt,
+ };
+}
+
+export function buildMetadataPrompt(
+ instruction: string,
+ extractionResponse: object,
+): ChatMessage {
+ return {
+ role: "user",
+ content: `Instruction: ${instruction}
+Extracted content: ${JSON.stringify(extractionResponse, null, 2)}`,
+ };
+}
+
+// observe
+export function buildObserveSystemPrompt(
+ userProvidedInstructions?: string,
+): ChatMessage {
+ const observeSystemPrompt = `
+You are helping the user automate the browser by finding elements based on what the user wants to observe in the page.
+
+You will be given:
+1. a instruction of elements to observe
+2. a hierarchical accessibility tree showing the semantic structure of the page. The tree is a hybrid of the DOM and the accessibility tree.
+
+Return an array of elements that match the instruction if they exist, otherwise return an empty array.`;
+ const content = observeSystemPrompt.replace(/\s+/g, " ");
+
+ return {
+ role: "system",
+ content: [content, buildUserInstructionsString(userProvidedInstructions)]
+ .filter(Boolean)
+ .join("\n\n"),
+ };
+}
+
+export function buildObserveUserMessage(
+ instruction: string,
+ domElements: string,
+): ChatMessage {
+ return {
+ role: "user",
+ content: `instruction: ${instruction}
+Accessibility Tree: \n${domElements}\n`,
+ };
+}
+
+export function buildActSystemPrompt(
+ userProvidedInstructions?: string,
+): ChatMessage {
+ const actSystemPrompt = `
+You are helping the user automate the browser by finding elements based on what action the user wants to take on the page
+
+You will be given:
+1. a user defined instruction about what action to take
+2. a hierarchical accessibility tree showing the semantic structure of the page. The tree is a hybrid of the DOM and the accessibility tree.
+
+Return the element that matches the instruction if it exists. Otherwise, return an empty object.`;
+ const content = actSystemPrompt.replace(/\s+/g, " ");
+
+ return {
+ role: "system",
+ content: [content, buildUserInstructionsString(userProvidedInstructions)]
+ .filter(Boolean)
+ .join("\n\n"),
+ };
+}
+
+export function buildActPrompt(
+ action: string,
+ supportedActions: string[],
+ variables?: Record,
+): string {
+ // Base instruction
+ let instruction = `Find the most relevant element to perform an action on given the following action: ${action}.
+ IF AND ONLY IF the action EXPLICITLY includes the word 'dropdown' and implies choosing/selecting an option from a dropdown, ignore the 'General Instructions' section, and follow the 'Dropdown Specific Instructions' section carefully.
+
+ General Instructions:
+ Provide an action for this element such as ${supportedActions.join(", ")}. Remember that to users, buttons and links look the same in most cases.
+ If the action is completely unrelated to a potential action to be taken on the page, return an empty object.
+ ONLY return one action. If multiple actions are relevant, return the most relevant one.
+ If the user is asking to scroll to a position on the page, e.g., 'halfway' or 0.75, etc, you must return the argument formatted as the correct percentage, e.g., '50%' or '75%', etc.
+ If the user is asking to scroll to the next chunk/previous chunk, choose the nextChunk/prevChunk method. No arguments are required here.
+ If the action implies a key press, e.g., 'press enter', 'press a', 'press space', etc., always choose the press method with the appropriate key as argument — e.g. 'a', 'Enter', 'Space'. Do not choose a click action on an on-screen keyboard. Capitalize the first character like 'Enter', 'Tab', 'Escape' only for special keys.
+
+ Dropdown Specific Instructions:
+ For interacting with dropdowns, there are two specific cases that you need to handle.
+
+ CASE 1: the element is a 'select' element.
+ - choose the selectOptionFromDropdown method,
+ - set the argument to the exact text of the option that should be selected,
+ - set twoStep to false.
+ CASE 2: the element is NOT a 'select' element:
+ - do not attempt to directly choose the element from the dropdown. You will need to click to expand the dropdown first. You will achieve this by following these instructions:
+ - choose the node that most closely corresponds to the given instruction EVEN if it is a 'StaticText' element, or otherwise does not appear to be interactable.
+ - choose the 'click' method
+ - set twoStep to true.
+ `;
+
+ // Add variable names (not values) to the instruction if any
+ if (variables && Object.keys(variables).length > 0) {
+ const variableNames = Object.keys(variables)
+ .map((key) => `%${key}%`)
+ .join(", ");
+ const variablesPrompt = `The following variables are available to use in the action: ${variableNames}. Fill the argument variables with the variable name.`;
+ instruction += ` ${variablesPrompt}`;
+ }
+
+ return instruction;
+}
+
+export function buildStepTwoPrompt(
+ originalUserAction: string,
+ previousAction: string,
+ supportedActions: string[],
+ variables?: Record,
+): string {
+ // Base instruction
+ let instruction = `
+ The original user action was: ${originalUserAction}.
+ You have just taken the following action which completed step 1 of 2: ${previousAction}.
+
+ Now, you must find the most relevant element to perform an action on in order to complete step 2 of 2.
+
+ General Instructions:
+ Provide an action for this element such as ${supportedActions.join(", ")}. Remember that to users, buttons and links look the same in most cases.
+ If the action is completely unrelated to a potential action to be taken on the page, return an empty object.
+ ONLY return one action. If multiple actions are relevant, return the most relevant one.
+ If the user is asking to scroll to a position on the page, e.g., 'halfway' or 0.75, etc, you must return the argument formatted as the correct percentage, e.g., '50%' or '75%', etc.
+ If the user is asking to scroll to the next chunk/previous chunk, choose the nextChunk/prevChunk method. No arguments are required here.
+ If the action implies a key press, e.g., 'press enter', 'press a', 'press space', etc., always choose the press method with the appropriate key as argument — e.g. 'a', 'Enter', 'Space'. Do not choose a click action on an on-screen keyboard. Capitalize the first character like 'Enter', 'Tab', 'Escape' only for special keys.
+ `;
+
+ // Add variable names (not values) to the instruction if any
+ if (variables && Object.keys(variables).length > 0) {
+ const variableNames = Object.keys(variables)
+ .map((key) => `%${key}%`)
+ .join(", ");
+ const variablesPrompt = `The following variables are available to use in the action: ${variableNames}. Fill the argument variables with the variable name.`;
+ instruction += ` ${variablesPrompt}`;
+ }
+
+ return instruction;
+}
+
+export function buildOperatorSystemPrompt(goal: string): ChatMessage {
+ return {
+ role: "system",
+ content: `You are a general-purpose agent whose job is to accomplish the user's goal across multiple model calls by running actions on the page.
+
+You will be given a goal and a list of steps that have been taken so far. Your job is to determine if either the user's goal has been completed or if there are still steps that need to be taken.
+
+# Your current goal
+${goal}
+
+# CRITICAL: You MUST use the provided tools to take actions. Do not just describe what you want to do - actually call the appropriate tools.
+
+# Available tools and when to use them:
+- \`act\`: Use this to interact with the page (click, type, navigate, etc.)
+- \`extract\`: Use this to get information from the page
+- \`goto\`: Use this to navigate to a specific URL
+- \`wait\`: Use this to wait for a period of time
+- \`navback\`: Use this to go back to the previous page
+- \`refresh\`: Use this to refresh the current page
+- \`close\`: Use this ONLY when the task is complete or cannot be achieved
+- External tools: Use any additional tools (like search tools) as needed for your goal
+
+# Important guidelines
+1. ALWAYS use tools - never just provide text responses about what you plan to do
+2. Break down complex actions into individual atomic steps
+3. For \`act\` commands, use only one action at a time, such as:
+ - Single click on a specific element
+ - Type into a single input field
+ - Select a single option
+4. Avoid combining multiple actions in one instruction
+5. If multiple actions are needed, they should be separate steps
+6. Only use \`close\` when the task is genuinely complete or impossible to achieve`,
+ };
+}
+
+export function buildCuaDefaultSystemPrompt(): string {
+ return `You are a helpful assistant that can use a web browser.\nDo not ask follow up questions, the user will trust your judgement. Today's date is ${new Date().toISOString().split("T")[0]}.`;
+}
+
+export function buildGoogleCUASystemPrompt(): ChatMessage {
+ return {
+ role: "system",
+ content: `You are a general-purpose browser agent whose job is to accomplish the user's goal.
+Today's date is ${new Date().toISOString().split("T")[0]}.
+You have access to a search tool; however, in most cases you should operate within the page/url the user has provided. ONLY use the search tool if you're stuck or the task is impossible to complete within the current page.
+You will be given a goal and a list of steps that have been taken so far. Avoid requesting the user for input as much as possible. Good luck!
+`,
+ };
+}
diff --git a/packages/core/lib/utils.ts b/packages/core/lib/utils.ts
new file mode 100644
index 000000000..57aac6af7
--- /dev/null
+++ b/packages/core/lib/utils.ts
@@ -0,0 +1,843 @@
+import { ZodSchemaValidationError } from "./v3/types/public/sdkErrors";
+import { Schema, Type } from "@google/genai";
+import { z, ZodTypeAny } from "zod";
+import z3 from "zod/v3";
+import { LogLine } from "./v3/types/public/logs";
+import { ModelProvider } from "./v3/types/public/model";
+import { ZodPathSegments } from "./v3/types/private/internal";
+import type { StagehandZodSchema } from "./v3/zodCompat";
+import { isZod4Schema } from "./v3/zodCompat";
+
+const ID_PATTERN = /^\d+-\d+$/;
+
+const zFactories = {
+ v4: z,
+ v3: z3 as unknown as typeof z,
+};
+
+export function getZFactory(schema: StagehandZodSchema): typeof z {
+ return isZod4Schema(schema) ? zFactories.v4 : zFactories.v3;
+}
+
+const TYPE_NAME_MAP: Record = {
+ ZodString: "string",
+ string: "string",
+ ZodNumber: "number",
+ number: "number",
+ ZodBoolean: "boolean",
+ boolean: "boolean",
+ ZodObject: "object",
+ object: "object",
+ ZodArray: "array",
+ array: "array",
+ ZodUnion: "union",
+ union: "union",
+ ZodIntersection: "intersection",
+ intersection: "intersection",
+ ZodOptional: "optional",
+ optional: "optional",
+ ZodNullable: "nullable",
+ nullable: "nullable",
+ ZodLiteral: "literal",
+ literal: "literal",
+ ZodEnum: "enum",
+ enum: "enum",
+ ZodDefault: "default",
+ default: "default",
+ ZodEffects: "effects",
+ effects: "effects",
+ pipe: "pipe",
+};
+
+function getZ4Def(schema: StagehandZodSchema) {
+ return (schema as SchemaInternals)._zod?.def as
+ | Record
+ | undefined;
+}
+
+function getZ4Bag(schema: StagehandZodSchema) {
+ return (schema as SchemaInternals)._zod?.bag as
+ | Record
+ | undefined;
+}
+
+function getZ3Def(schema: StagehandZodSchema) {
+ return (schema as SchemaInternals)._def as
+ | Record
+ | undefined;
+}
+
+function getObjectShape(
+ schema: StagehandZodSchema,
+): Record | undefined {
+ const z4Shape = getZ4Def(schema)?.shape as
+ | Record
+ | undefined;
+ if (z4Shape) {
+ return z4Shape;
+ }
+
+ const z3Shape = getZ3Def(schema)?.shape;
+ if (!z3Shape) {
+ return undefined;
+ }
+
+ if (typeof z3Shape === "function") {
+ return (z3Shape as () => Record)();
+ }
+
+ return z3Shape as Record;
+}
+
+function getArrayElement(
+ schema: StagehandZodSchema,
+): StagehandZodSchema | undefined {
+ return (getZ4Def(schema)?.element ?? getZ3Def(schema)?.type) as
+ | StagehandZodSchema
+ | undefined;
+}
+
+function getInnerType(
+ schema: StagehandZodSchema,
+): StagehandZodSchema | undefined {
+ return (getZ4Def(schema)?.innerType ?? getZ3Def(schema)?.innerType) as
+ | StagehandZodSchema
+ | undefined;
+}
+
+function getUnionOptions(
+ schema: StagehandZodSchema,
+): StagehandZodSchema[] | undefined {
+ const z4Options = getZ4Def(schema)?.options;
+ if (Array.isArray(z4Options)) {
+ return z4Options as StagehandZodSchema[];
+ }
+ const z3Options = getZ3Def(schema)?.options;
+ return Array.isArray(z3Options)
+ ? (z3Options as StagehandZodSchema[])
+ : undefined;
+}
+
+function getIntersectionSides(schema: StagehandZodSchema): {
+ left?: StagehandZodSchema;
+ right?: StagehandZodSchema;
+} {
+ const z4Def = getZ4Def(schema);
+ if (z4Def?.left || z4Def?.right) {
+ return {
+ left: z4Def?.left as StagehandZodSchema | undefined,
+ right: z4Def?.right as StagehandZodSchema | undefined,
+ };
+ }
+ const z3Def = getZ3Def(schema);
+ return {
+ left: z3Def?.left as StagehandZodSchema | undefined,
+ right: z3Def?.right as StagehandZodSchema | undefined,
+ };
+}
+
+function getEnumValues(schema: StagehandZodSchema): string[] | undefined {
+ const z4Entries = getZ4Def(schema)?.entries;
+ if (z4Entries && typeof z4Entries === "object") {
+ return Object.values(z4Entries as Record);
+ }
+ const z3Values = getZ3Def(schema)?.values;
+ return Array.isArray(z3Values) ? (z3Values as string[]) : undefined;
+}
+
+function getLiteralValues(schema: StagehandZodSchema): unknown[] {
+ const z4Values = getZ4Def(schema)?.values;
+ if (Array.isArray(z4Values)) {
+ return z4Values as unknown[];
+ }
+ const value = getZ3Def(schema)?.value;
+ return typeof value !== "undefined" ? [value] : [];
+}
+
+function getStringChecks(schema: StagehandZodSchema): unknown[] {
+ const z4Checks = getZ4Def(schema)?.checks;
+ if (Array.isArray(z4Checks)) {
+ return z4Checks;
+ }
+ const z3Checks = getZ3Def(schema)?.checks;
+ return Array.isArray(z3Checks) ? z3Checks : [];
+}
+
+function getStringFormat(schema: StagehandZodSchema): string | undefined {
+ const bagFormat = getZ4Bag(schema)?.format;
+ if (typeof bagFormat === "string") {
+ return bagFormat;
+ }
+ const z4Format = getZ4Def(schema)?.format;
+ if (typeof z4Format === "string") {
+ return z4Format;
+ }
+ const z3Format = getZ3Def(schema)?.format;
+ return typeof z3Format === "string" ? z3Format : undefined;
+}
+
+function getPipeEndpoints(schema: StagehandZodSchema): {
+ in?: StagehandZodSchema;
+ out?: StagehandZodSchema;
+} {
+ const z4Def = getZ4Def(schema);
+ if (z4Def?.in || z4Def?.out) {
+ return {
+ in: z4Def?.in as StagehandZodSchema | undefined,
+ out: z4Def?.out as StagehandZodSchema | undefined,
+ };
+ }
+ return {};
+}
+
+function getEffectsBaseSchema(
+ schema: StagehandZodSchema,
+): StagehandZodSchema | undefined {
+ return getZ3Def(schema)?.schema as StagehandZodSchema | undefined;
+}
+
+type SchemaInternals = {
+ _zod?: { def?: Record; bag?: Record };
+ _def?: Record;
+};
+
+export function validateZodSchema(schema: StagehandZodSchema, data: unknown) {
+ const result = schema.safeParse(data);
+
+ if (result.success) {
+ return true;
+ }
+ throw new ZodSchemaValidationError(data, result.error.format());
+}
+
+/**
+ * Detects if the code is running in the Bun runtime environment.
+ * @returns {boolean} True if running in Bun, false otherwise.
+ */
+export function isRunningInBun(): boolean {
+ return (
+ typeof process !== "undefined" &&
+ typeof process.versions !== "undefined" &&
+ "bun" in process.versions
+ );
+}
+
+/*
+ * Helper functions for converting between Gemini and Zod schemas
+ */
+function decorateGeminiSchema(
+ geminiSchema: Schema,
+ zodSchema: z.ZodTypeAny,
+): Schema {
+ if (geminiSchema.nullable === undefined) {
+ geminiSchema.nullable = zodSchema.isOptional();
+ }
+
+ if (zodSchema.description) {
+ geminiSchema.description = zodSchema.description;
+ }
+
+ return geminiSchema;
+}
+
+export function toGeminiSchema(zodSchema: StagehandZodSchema): Schema {
+ const normalizedSchema = zodSchema as z.ZodTypeAny;
+ const zodType = getZodType(zodSchema);
+ switch (zodType) {
+ case "array": {
+ const element = getArrayElement(zodSchema) ?? z.any();
+ return decorateGeminiSchema(
+ {
+ type: Type.ARRAY,
+ items: toGeminiSchema(element),
+ },
+ normalizedSchema,
+ );
+ }
+ case "object": {
+ const properties: Record = {};
+ const required: string[] = [];
+
+ const shape = getObjectShape(zodSchema);
+ if (shape) {
+ Object.entries(shape).forEach(
+ ([key, value]: [string, StagehandZodSchema]) => {
+ properties[key] = toGeminiSchema(value);
+ if (getZodType(value) !== "optional") {
+ required.push(key);
+ }
+ },
+ );
+ }
+
+ return decorateGeminiSchema(
+ {
+ type: Type.OBJECT,
+ properties,
+ required: required.length > 0 ? required : undefined,
+ },
+ normalizedSchema,
+ );
+ }
+ case "string":
+ return decorateGeminiSchema(
+ {
+ type: Type.STRING,
+ },
+ normalizedSchema,
+ );
+ case "number":
+ return decorateGeminiSchema(
+ {
+ type: Type.NUMBER,
+ },
+ normalizedSchema,
+ );
+ case "boolean":
+ return decorateGeminiSchema(
+ {
+ type: Type.BOOLEAN,
+ },
+ normalizedSchema,
+ );
+ case "enum": {
+ const values = getEnumValues(zodSchema);
+ return decorateGeminiSchema(
+ {
+ type: Type.STRING,
+ enum: values,
+ },
+ normalizedSchema,
+ );
+ }
+ case "default":
+ case "nullable":
+ case "optional": {
+ const innerType = getInnerType(zodSchema) ?? z.any();
+ const innerSchema = toGeminiSchema(innerType);
+ return decorateGeminiSchema(
+ {
+ ...innerSchema,
+ nullable: true,
+ },
+ normalizedSchema,
+ );
+ }
+ case "literal": {
+ const values = getLiteralValues(zodSchema);
+ return decorateGeminiSchema(
+ {
+ type: Type.STRING,
+ enum: values as string[],
+ },
+ normalizedSchema,
+ );
+ }
+ case "pipe": {
+ const endpoints = getPipeEndpoints(zodSchema);
+ if (endpoints.in) {
+ return toGeminiSchema(endpoints.in);
+ }
+ return decorateGeminiSchema(
+ {
+ type: Type.STRING,
+ },
+ normalizedSchema,
+ );
+ }
+ // Standalone transforms and any unknown types fall through to default
+ default:
+ return decorateGeminiSchema(
+ {
+ type: Type.STRING,
+ },
+ normalizedSchema,
+ );
+ }
+}
+
+// Helper function to check the type of Zod schema
+export function getZodType(schema: StagehandZodSchema): string {
+ const schemaWithDef = schema as SchemaInternals & {
+ _zod?: { def?: { type?: string } };
+ };
+ const rawType =
+ (schemaWithDef._zod?.def?.type as string | undefined) ??
+ (schemaWithDef._def?.typeName as string | undefined) ??
+ (schemaWithDef._def?.type as string | undefined);
+
+ if (!rawType) {
+ return "unknown";
+ }
+
+ return TYPE_NAME_MAP[rawType] ?? rawType;
+}
+
+/**
+ * Recursively traverses a given Zod schema, scanning for any fields of type `z.string().url()`.
+ * For each such field, it replaces the `z.string().url()` with `z.number()`.
+ *
+ * This function is used internally by higher-level utilities (e.g., transforming entire object schemas)
+ * and handles nested objects, arrays, unions, intersections, optionals.
+ *
+ * @param schema - The Zod schema to transform.
+ * @param currentPath - An array of string/number keys representing the current schema path (used internally for recursion).
+ * @returns A two-element tuple:
+ * 1. The updated Zod schema, with any `.url()` fields replaced by `z.number()`.
+ * 2. An array of {@link ZodPathSegments} objects representing each replaced field, including the path segments.
+ */
+export function transformSchema(
+ schema: StagehandZodSchema,
+ currentPath: Array,
+): [StagehandZodSchema, ZodPathSegments[]] {
+ if (isKind(schema, "string")) {
+ const checks = getStringChecks(schema);
+ const format = getStringFormat(schema);
+ const hasUrlCheck =
+ checks.some((check) => {
+ const candidate = check as {
+ kind?: string;
+ format?: string;
+ _zod?: { def?: { check?: string; format?: string } };
+ };
+ return (
+ candidate.kind === "url" ||
+ candidate.format === "url" ||
+ candidate._zod?.def?.check === "url" ||
+ candidate._zod?.def?.format === "url"
+ );
+ }) || format === "url";
+
+ if (hasUrlCheck) {
+ return [makeIdStringSchema(schema), [{ segments: [] }]];
+ }
+ return [schema, []];
+ }
+
+ if (isKind(schema, "object")) {
+ const shape = getObjectShape(schema);
+ if (!shape) {
+ return [schema, []];
+ }
+ const newShape: Record = {};
+ const urlPaths: ZodPathSegments[] = [];
+ let changed = false;
+
+ for (const key of Object.keys(shape)) {
+ const child = shape[key];
+ const [transformedChild, childPaths] = transformSchema(child, [
+ ...currentPath,
+ key,
+ ]);
+ if (transformedChild !== child) {
+ changed = true;
+ }
+ newShape[key] = transformedChild;
+ childPaths.forEach((cp) => {
+ urlPaths.push({ segments: [key, ...cp.segments] });
+ });
+ }
+
+ if (changed) {
+ const factory = getZFactory(schema);
+ return [
+ factory.object(newShape as Record