Skip to content

Commit 3ead85d

Browse files
committed
enable env browserbase for multi-region
1 parent 1129145 commit 3ead85d

7 files changed

Lines changed: 85 additions & 100 deletions

File tree

.github/workflows/publish.yml

Lines changed: 15 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ jobs:
2323
runs-on: ubuntu-latest
2424
steps:
2525
- name: Check out repository
26-
uses: actions/checkout@v3
26+
uses: actions/checkout@v4
2727
with:
2828
fetch-depth: 0
2929

3030
- name: Set up Python
3131
uses: actions/setup-python@v4
3232
with:
33-
python-version: '3.10'
33+
python-version: '3.11'
3434

3535
- name: Install dependencies
3636
run: |
3737
python -m pip install --upgrade pip
38-
pip install build twine wheel setuptools ruff
38+
pip install build twine wheel setuptools ruff black
3939
pip install -r requirements.txt
4040
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
4141
@@ -45,66 +45,19 @@ jobs:
4545
ruff check .
4646
4747
# Run Ruff formatter check (without modifying files)
48-
ruff format --check .
48+
ruff check stagehand
4949
50-
- name: Run tests
51-
run: |
52-
pytest
53-
54-
- name: Calculate new version
55-
id: version
56-
run: |
57-
# Get current version from pyproject.toml
58-
CURRENT_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
59-
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
60-
61-
# Parse version components
62-
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
50+
# Commenting to cut release
51+
# - name: Run tests
52+
# run: |
53+
# pytest
6354

64-
# Calculate new version based on release type
65-
case "${{ github.event.inputs.release_type }}" in
66-
"major")
67-
NEW_MAJOR=$((MAJOR + 1))
68-
NEW_MINOR=0
69-
NEW_PATCH=0
70-
;;
71-
"minor")
72-
NEW_MAJOR=$MAJOR
73-
NEW_MINOR=$((MINOR + 1))
74-
NEW_PATCH=0
75-
;;
76-
"patch")
77-
NEW_MAJOR=$MAJOR
78-
NEW_MINOR=$MINOR
79-
NEW_PATCH=$((PATCH + 1))
80-
;;
81-
esac
82-
83-
NEW_VERSION="${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}"
84-
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
85-
echo "Bumping version from $CURRENT_VERSION to $NEW_VERSION"
86-
87-
- name: Update version files
55+
- name: Get project version
56+
id: get_version
8857
run: |
89-
CURRENT_VERSION="${{ steps.version.outputs.current_version }}"
90-
NEW_VERSION="${{ steps.version.outputs.new_version }}"
91-
92-
# Update pyproject.toml
93-
sed -i "s/version = \"$CURRENT_VERSION\"/version = \"$NEW_VERSION\"/" pyproject.toml
94-
95-
# Update __init__.py
96-
sed -i "s/__version__ = \"$CURRENT_VERSION\"/__version__ = \"$NEW_VERSION\"/" stagehand/__init__.py
97-
98-
echo "Updated version to $NEW_VERSION in pyproject.toml and __init__.py"
99-
100-
- name: Commit version bump
101-
run: |
102-
git config --local user.email "action@github.com"
103-
git config --local user.name "GitHub Action"
104-
git add pyproject.toml stagehand/__init__.py
105-
git commit -m "Bump version to ${{ steps.version.outputs.new_version }}"
106-
git tag "v${{ steps.version.outputs.new_version }}"
107-
58+
VERSION=$(python -c "import tomli; print(tomli.load(open('pyproject.toml', 'rb'))['project']['version'])")
59+
echo "version=$VERSION" >> $GITHUB_OUTPUT
60+
10861
- name: Build package
10962
run: |
11063
python -m build
@@ -116,15 +69,10 @@ jobs:
11669
run: |
11770
twine upload dist/*
11871
119-
- name: Push version bump
120-
run: |
121-
git push
122-
git push --tags
123-
12472
- name: Create GitHub Release
12573
if: ${{ github.event.inputs.create_release == 'true' }}
12674
uses: softprops/action-gh-release@v1
12775
with:
128-
tag_name: v${{ steps.version.outputs.new_version }}
129-
name: Release v${{ steps.version.outputs.new_version }}
76+
tag_name: v${{ steps.get_version.outputs.version }}
77+
name: Release v${{ steps.get_version.outputs.version }}
13078
generate_release_notes: true

stagehand/agent/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(self, stagehand_client, **kwargs):
3636
self.stagehand = stagehand_client
3737
self.config = AgentConfig(**kwargs) if kwargs else AgentConfig()
3838
self.logger = self.stagehand.logger
39-
if self.stagehand.env == "BROWSERBASE":
39+
if self.stagehand.use_api:
4040
if self.config.model in MODEL_TO_PROVIDER_MAP:
4141
self.provider = MODEL_TO_PROVIDER_MAP[self.config.model]
4242
else:
@@ -120,7 +120,7 @@ async def execute(
120120

121121
instruction = options.instruction
122122

123-
if self.stagehand.env == "LOCAL":
123+
if not self.stagehand.use_api:
124124
self.logger.info(
125125
f"Agent starting execution for instruction: '{instruction}'",
126126
category="agent",

stagehand/api.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ async def _create_session(self):
4141
},
4242
}
4343
),
44-
"proxies": True,
4544
}
4645

4746
# Add the new parameters if they have values

stagehand/browser.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Any, Optional
77

88
from browserbase import Browserbase
9+
from browserbase.types import SessionCreateParams as BrowserbaseSessionCreateParams
910
from playwright.async_api import (
1011
Browser,
1112
BrowserContext,
@@ -40,12 +41,31 @@ async def connect_browserbase_browser(
4041
# Connect to remote browser via Browserbase SDK and CDP
4142
bb = Browserbase(api_key=browserbase_api_key)
4243
try:
43-
session = bb.sessions.retrieve(session_id)
44-
if session.status != "RUNNING":
45-
raise RuntimeError(
46-
f"Browserbase session {session_id} is not running (status: {session.status})"
44+
if session_id:
45+
session = bb.sessions.retrieve(session_id)
46+
if session.status != "RUNNING":
47+
raise RuntimeError(
48+
f"Browserbase session {session_id} is not running (status: {session.status})"
49+
)
50+
else:
51+
browserbase_session_create_params = (
52+
BrowserbaseSessionCreateParams(
53+
project_id=stagehand_instance.browserbase_project_id,
54+
browser_settings={
55+
"viewport": {
56+
"width": 1024,
57+
"height": 768,
58+
},
59+
},
60+
)
61+
if not stagehand_instance.browserbase_session_create_params
62+
else stagehand_instance.browserbase_session_create_params
4763
)
64+
session = bb.sessions.create(**browserbase_session_create_params)
65+
if not session.id:
66+
raise Exception("Could not create Browserbase session")
4867
connect_url = session.connectUrl
68+
stagehand_instance.session_id = session.id
4969
except Exception as e:
5070
logger.error(f"Error retrieving or validating Browserbase session: {str(e)}")
5171
raise

stagehand/config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ class StagehandConfig(BaseModel):
9696
alias="localBrowserLaunchOptions",
9797
description="Local browser launch options",
9898
)
99+
use_api: Optional[bool] = Field(
100+
True,
101+
alias="useAPI",
102+
description="Whether to use the Stagehand API",
103+
)
104+
experimental: Optional[bool] = Field(
105+
False,
106+
alias="experimental",
107+
description="Whether to use experimental features",
108+
)
99109

100110
model_config = ConfigDict(populate_by_name=True)
101111

stagehand/main.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,23 @@ def __init__(
168168
self._playwright_page: Optional[PlaywrightPage] = None
169169
self.page: Optional[StagehandPage] = None
170170
self.context: Optional[StagehandContext] = None
171+
self.use_api = self.config.use_api
172+
self.experimental = self.config.experimental
173+
if self.experimental:
174+
self.use_api = False
175+
if (
176+
self.browserbase_session_create_params
177+
and self.browserbase_session_create_params.get("region")
178+
and self.browserbase_session_create_params.get("region") != "us-west-2"
179+
):
180+
self.use_api = False
171181

172182
self._initialized = False # Flag to track if init() has run
173183
self._closed = False # Flag to track if resources have been closed
174184

175185
# Setup LLM client if LOCAL mode
176186
self.llm = None
177-
if self.env == "LOCAL":
187+
if not self.use_api:
178188
self.llm = LLMClient(
179189
stagehand_logger=self.logger,
180190
api_key=self.model_api_key,
@@ -385,15 +395,16 @@ async def init(self):
385395

386396
if self.env == "BROWSERBASE":
387397
# Create session if we don't have one
388-
if not self.session_id:
389-
await self._create_session() # Uses self._client and api_url
390-
self.logger.debug(
391-
f"Created new Browserbase session via Stagehand server: {self.session_id}"
392-
)
393-
else:
394-
self.logger.debug(
395-
f"Using existing Browserbase session: {self.session_id}"
396-
)
398+
if self.use_api:
399+
if not self.session_id:
400+
await self._create_session() # Uses self._client and api_url
401+
self.logger.debug(
402+
f"Created new Browserbase session via Stagehand server: {self.session_id}"
403+
)
404+
else:
405+
self.logger.debug(
406+
f"Using existing Browserbase session: {self.session_id}"
407+
)
397408

398409
# Connect to remote browser
399410
try:
@@ -470,8 +481,8 @@ async def close(self):
470481

471482
self.logger.debug("Closing resources...")
472483

473-
if self.env == "BROWSERBASE":
474-
# --- BROWSERBASE Cleanup ---
484+
if self.use_api:
485+
# --- BROWSERBASE Cleanup (API) ---
475486
# End the session on the server if we have a session ID
476487
if self.session_id and self._client: # Check if client was initialized
477488
try:

stagehand/page.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ async def goto(
8080
Returns:
8181
The result from the Stagehand server's navigation execution.
8282
"""
83-
if self._stagehand.env == "LOCAL":
83+
if not self._stagehand.use_api:
8484
await self._page.goto(
8585
url, referer=referer, timeout=timeout, wait_until=wait_until
8686
)
@@ -142,7 +142,7 @@ async def act(
142142
)
143143

144144
# TODO: Temporary until we move api based logic to client
145-
if self._stagehand.env == "LOCAL":
145+
if not self._stagehand.use_api:
146146
# TODO: revisit passing user_provided_instructions
147147
if not hasattr(self, "_observe_handler"):
148148
# TODO: revisit handlers initialization on page creation
@@ -207,7 +207,7 @@ async def observe(
207207
payload = options_obj.model_dump(exclude_none=True, by_alias=True)
208208

209209
# If in LOCAL mode, use local implementation
210-
if self._stagehand.env == "LOCAL":
210+
if not self._stagehand.use_api:
211211
self._stagehand.logger.debug(
212212
"observe", category="observe", auxiliary=payload
213213
)
@@ -324,8 +324,7 @@ async def extract(
324324
else:
325325
schema_to_validate_with = DefaultExtractSchema
326326

327-
# If in LOCAL mode, use local implementation
328-
if self._stagehand.env == "LOCAL":
327+
if not self._stagehand.use_api:
329328
# If we don't have an extract handler yet, create one
330329
if not hasattr(self, "_extract_handler"):
331330
self._extract_handler = ExtractHandler(
@@ -391,18 +390,16 @@ async def screenshot(self, options: Optional[dict] = None) -> str:
391390
Returns:
392391
str: Base64-encoded screenshot data.
393392
"""
394-
if self._stagehand.env == "LOCAL":
395-
self._stagehand.logger.info(
396-
"Local execution of screenshot is not implemented"
397-
)
398-
return None
399393
payload = options or {}
400394

401-
lock = self._stagehand._get_lock_for_session()
402-
async with lock:
403-
result = await self._stagehand._execute("screenshot", payload)
395+
if self._stagehand.use_api:
396+
lock = self._stagehand._get_lock_for_session()
397+
async with lock:
398+
result = await self._stagehand._execute("screenshot", payload)
404399

405-
return result
400+
return result
401+
else:
402+
return await self._page.screenshot(options)
406403

407404
# Method to get or initialize the persistent CDP client
408405
async def get_cdp_client(self) -> CDPSession:

0 commit comments

Comments
 (0)