Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: '0.9.13'
version: '0.10.2'

- name: Install dependencies
run: uv sync --all-extras
Expand All @@ -46,7 +46,7 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: '0.9.13'
version: '0.10.2'

- name: Install dependencies
run: uv sync --all-extras
Expand All @@ -55,14 +55,18 @@ jobs:
run: uv build

- name: Get GitHub OIDC Token
if: github.repository == 'stainless-sdks/stagehand-python'
if: |-
github.repository == 'stainless-sdks/stagehand-python' &&
!startsWith(github.ref, 'refs/heads/stl/')
id: github-oidc
uses: actions/github-script@v8
with:
script: core.setOutput('github_token', await core.getIDToken());

- name: Upload tarball
if: github.repository == 'stainless-sdks/stagehand-python'
if: |-
github.repository == 'stainless-sdks/stagehand-python' &&
!startsWith(github.ref, 'refs/heads/stl/')
env:
URL: https://pkg.stainless.com/s
AUTH: ${{ steps.github-oidc.outputs.github_token }}
Expand All @@ -80,7 +84,7 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: '0.9.13'
version: '0.10.2'

- name: Bootstrap
run: ./scripts/bootstrap
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ jobs:
matrix:
include:
- os: ubuntu-latest
binary_name: stagehand-server-linux-x64
binary_name: stagehand-server-v3-linux-x64
output_path: src/stagehand/_sea/stagehand-linux-x64
wheel_platform_tag: manylinux2014_x86_64
- os: macos-latest
binary_name: stagehand-server-darwin-arm64
binary_name: stagehand-server-v3-darwin-arm64
output_path: src/stagehand/_sea/stagehand-darwin-arm64
wheel_platform_tag: macosx_11_0_arm64
- os: macos-15-intel
binary_name: stagehand-server-darwin-x64
binary_name: stagehand-server-v3-darwin-x64
output_path: src/stagehand/_sea/stagehand-darwin-x64
wheel_platform_tag: macosx_10_9_x86_64
- os: windows-latest
binary_name: stagehand-server-win32-x64.exe
binary_name: stagehand-server-v3-win32-x64.exe
output_path: src/stagehand/_sea/stagehand-win32-x64.exe
wheel_platform_tag: win_amd64

Expand All @@ -45,7 +45,7 @@ jobs:
with:
version: "0.9.13"

- name: Resolve latest stagehand/server release
- name: Resolve latest stagehand/server-v3 release
id: stagehand-server-release
uses: actions/github-script@v6
with:
Expand All @@ -56,12 +56,12 @@ jobs:
repo: 'stagehand',
per_page: 100,
});
const release = data.find(r => typeof r.tag_name === 'string' && r.tag_name.startsWith('stagehand-server/v'));
const release = data.find(r => typeof r.tag_name === 'string' && r.tag_name.startsWith('stagehand-server-v3/v'));
if (!release) {
core.setFailed('No stagehand-server/v* release found in browserbase/stagehand');
core.setFailed('No stagehand-server-v3/v* release found in browserbase/stagehand');
return;
}
core.info(`Using stagehand/server release tag: ${release.tag_name}`);
core.info(`Using stagehand/server-v3 release tag: ${release.tag_name}`);
core.setOutput('tag', release.tag_name);
core.setOutput('id', String(release.id));

Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "3.6.0"
".": "3.7.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 8
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-87b4d9e349de9d33d5d89439f7ac9507133700a9f072fdf0d756471961768d2c.yml
openapi_spec_hash: 0f6ae6d10a0227a3482914728cf901ba
config_hash: 7baf2daccae5913216bb74c52d63eaff
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-573d364768ac1902ee5ed8b2485d3b293bda0ea8ff7898aef1a3fd6be79b594a.yml
openapi_spec_hash: 107ec840f4330885dd2232a05a66fed7
config_hash: 0209737a4ab2a71afececb0ff7459998
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 3.7.0 (2026-03-11)

Full Changelog: [v3.6.0...v3.7.0](https://github.com/browserbase/stagehand-python/compare/v3.6.0...v3.7.0)

### Features

* Add bedrock to provider enum in Zod schemas and OpenAPI spec ([5ace8c9](https://github.com/browserbase/stagehand-python/commit/5ace8c9ee306ff8cc09c258500c24b6b55e9562b))
* Add missing cdpHeaders field to v3 server openapi spec ([7c17bc2](https://github.com/browserbase/stagehand-python/commit/7c17bc237e604ae38ca6772deb02a378d5117f81))


### Chores

* **ci:** bump uv version ([84a841c](https://github.com/browserbase/stagehand-python/commit/84a841cd29648432d713f10b37a530e18942a3f0))
* **ci:** skip uploading artifacts on stainless-internal branches ([291b296](https://github.com/browserbase/stagehand-python/commit/291b296a33c5be942422946051925ad6c500679b))

## 3.6.0 (2026-02-25)

Full Changelog: [v3.5.0...v3.6.0](https://github.com/browserbase/stagehand-python/compare/v3.5.0...v3.6.0)
Expand Down
12 changes: 6 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ $ uv run python scripts/download-binary.py

This will:
- Detect your platform (macOS, Linux, Windows) and architecture (x64, arm64)
- Download the latest stagehand-server binary from GitHub releases
- Download the latest stagehand-server-v3 binary from GitHub releases
- Place it in `bin/sea/` where the SDK expects to find it

### Manual download (alternative)

You can also manually download from [GitHub releases](https://github.com/browserbase/stagehand/releases):

1. Find the latest `stagehand/server vX.X.X` release
1. Find the latest `stagehand/server-v3 vX.X.X` release
2. Download the binary for your platform:
- macOS ARM: `stagehand-server-darwin-arm64`
- macOS Intel: `stagehand-server-darwin-x64`
- Linux: `stagehand-server-linux-x64` or `stagehand-server-linux-arm64`
- Windows: `stagehand-server-win32-x64.exe` or `stagehand-server-win32-arm64.exe`
- macOS ARM: `stagehand-server-v3-darwin-arm64`
- macOS Intel: `stagehand-server-v3-darwin-x64`
- Linux: `stagehand-server-v3-linux-x64` or `stagehand-server-v3-linux-arm64`
- Windows: `stagehand-server-v3-win32-x64.exe` or `stagehand-server-v3-win32-arm64.exe`
3. Rename it to match the expected format (remove `-server` from the name):
- `stagehand-darwin-arm64`, `stagehand-linux-x64`, `stagehand-win32-x64.exe`, etc.
4. Place it in `bin/sea/` directory
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ Examples and dependencies:
- `examples/byob_example.py`: Playwright + Playwright browsers
- `examples/pydoll_tab_example.py`: `pydoll-python` (Python 3.10+)

Multiregion support: see `examples/local_server_multiregion_browser_example.py`.

Run any example:

```bash
Expand Down
6 changes: 3 additions & 3 deletions RELEASE_WORKFLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ This repo publishes the `stagehand` Python package to PyPI when a **GitHub Relea
- Publishes the GitHub Release + git tag.
4. Wait for GitHub Actions to publish to PyPI (automatic).
- Trigger: GitHub Release `published` event runs `.github/workflows/publish-pypi.yml`.
- Builds platform wheels that embed the Stagehand server binary (downloaded from the latest `stagehand-server/v*` GitHub Release in `browserbase/stagehand`), then publishes to PyPI.
- Builds platform wheels that embed the Stagehand server binary (downloaded from the latest `stagehand-server-v3/v*` GitHub Release in `browserbase/stagehand`), then publishes to PyPI.

## Important implementation notes

- **Server binary bundling into wheels**
- `.github/workflows/publish-pypi.yml` downloads the prebuilt Stagehand server SEA binary from the latest `stagehand-server/v*` GitHub Release in `browserbase/stagehand`, then places it into `src/stagehand/_sea/*` before running `uv build --wheel`.
- `.github/workflows/publish-pypi.yml` downloads the prebuilt Stagehand server SEA binary from the latest `stagehand-server-v3/v*` GitHub Release in `browserbase/stagehand`, then places it into `src/stagehand/_sea/*` before running `uv build --wheel`.
- **Stagehand server version selection (current behavior)**
- `publish-pypi.yml` resolves the latest GitHub Release tag matching `stagehand-server/v*` from `browserbase/stagehand` and downloads the matching `stagehand-server-<platform>` release asset for each wheel build.
- `publish-pypi.yml` resolves the latest GitHub Release tag matching `stagehand-server-v3/v*` from `browserbase/stagehand` and downloads the matching `stagehand-server-v3-<platform>` release asset for each wheel build.
- **Secrets**
- PyPI publish uses `secrets.STAGEHAND_PYPI_TOKEN || secrets.PYPI_TOKEN`.
- `.github/workflows/release-doctor.yml` runs `bin/check-release-environment` on qualifying PRs and fails if `PYPI_TOKEN` is missing.
173 changes: 173 additions & 0 deletions examples/local_server_multiregion_browser_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
"""
Example: local Stagehand server + multiregion Browserbase browser in eu-central-1.

What this demonstrates:
- Run Stagehand server locally (SEA) with Browserbase cloud browser
- Request a Browserbase session in eu-central-1
- Attach Playwright to the same browser via CDP (`cdp_url`)
- Stream SSE events by default for observe/act/extract/execute
- Run the full flow: start → observe → act → extract → agent/execute → end

Environment variables required:
- MODEL_API_KEY
- BROWSERBASE_API_KEY
- BROWSERBASE_PROJECT_ID

Optional:
- STAGEHAND_BASE_URL (defaults to http://127.0.0.1:3000 when server="local")
"""

from __future__ import annotations

import os
import sys
from typing import Any, Optional

from env import load_example_env

from stagehand import Stagehand


def _print_stream_events(stream: Any, label: str) -> object | None:
result_payload: object | None = None
for event in stream:
if event.type == "log":
print(f"[{label}][log] {event.data.message}")
continue

status = event.data.status
print(f"[{label}][system] status={status}")
if status == "finished":
result_payload = event.data.result
elif status == "error":
error_message = event.data.error or "unknown error"
raise RuntimeError(f"{label} stream reported error: {error_message}")

return result_payload


def main() -> None:
load_example_env()
model_api_key = os.environ.get("MODEL_API_KEY")
if not model_api_key:
sys.exit("Set the MODEL_API_KEY environment variable to run this example.")

bb_api_key = os.environ.get("BROWSERBASE_API_KEY")
bb_project_id = os.environ.get("BROWSERBASE_PROJECT_ID")
if not bb_api_key or not bb_project_id:
sys.exit(
"Set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID to run this example."
)

try:
from playwright.sync_api import sync_playwright # type: ignore[import-not-found]
except Exception:
sys.exit(
"Playwright is not installed. Install it with:\n"
" uv pip install playwright\n"
"and ensure browsers are installed (e.g. `playwright install chromium`)."
)

session_id: Optional[str] = None

with Stagehand(
server="local",
browserbase_api_key=bb_api_key,
browserbase_project_id=bb_project_id,
model_api_key=model_api_key,
local_openai_api_key=model_api_key,
local_ready_timeout_s=30.0,
) as client:
print("⏳ Starting Stagehand session (local server + Browserbase browser)...")
session = client.sessions.start(
model_name="anthropic/claude-sonnet-4-6",
browser={"type": "browserbase"},
browserbase_session_create_params={"region": "eu-central-1"},
verbose=2,
)
session_id = session.id

cdp_url = session.data.cdp_url
if not cdp_url:
sys.exit(
"No cdp_url returned from the API for this session; cannot attach Playwright."
)

print(f"✅ Session started: {session_id}")
print("🔌 Connecting Playwright to the same browser over CDP...")

try:
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(cdp_url)
try:
context = browser.contexts[0] if browser.contexts else browser.new_context()
page = context.pages[0] if context.pages else context.new_page()

page.goto("https://example.com", wait_until="domcontentloaded")

print("👀 Stagehand.observe(page=...) with SSE streaming...")
observe_stream = session.observe(
instruction="Find the most relevant click target on this page",
page=page,
stream_response=True,
x_stream_response="true",
)
observe_result = _print_stream_events(observe_stream, "observe")

actions = observe_result or []
if not actions:
print("No actions found; ending session.")
return

print("🖱️ Stagehand.act(page=...) with SSE streaming...")
act_stream = session.act(
input=actions[0],
page=page,
stream_response=True,
x_stream_response="true",
)
_ = _print_stream_events(act_stream, "act")

print("🧠 Stagehand.extract(page=...) with SSE streaming...")
extract_stream = session.extract(
instruction="Extract the page title and the primary heading (h1) text",
schema={
"type": "object",
"properties": {
"title": {"type": "string"},
"h1": {"type": "string"},
},
"required": ["title", "h1"],
"additionalProperties": False,
},
page=page,
stream_response=True,
x_stream_response="true",
)
extracted = _print_stream_events(extract_stream, "extract")
print("Extracted:", extracted)

print("🤖 Stagehand.execute(page=...) with SSE streaming...")
execute_stream = session.execute(
agent_config={"model": "anthropic/claude-opus-4-6"},
execute_options={
"instruction": (
"Open the 'Learn more' link if present and summarize the destination in one sentence."
),
"max_steps": 5,
},
page=page,
stream_response=True,
x_stream_response="true",
)
execute_result = _print_stream_events(execute_stream, "execute")
print("Execute result:", execute_result)
finally:
browser.close()
finally:
session.end()
print("✅ Session ended.")


if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "stagehand"
version = "3.6.0"
version = "3.7.0"
description = "The official Python library for the stagehand API"
dynamic = ["readme"]
license = "MIT"
Expand Down
Loading
Loading