Skip to content

Commit 3ec0b10

Browse files
authored
Merge pull request #50 from stainless-sdks/STG-1293
STG-1293: add local server multiregion example
2 parents 5ace8c9 + eedb1f4 commit 3ec0b10

3 files changed

Lines changed: 176 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ Examples and dependencies:
112112
- `examples/byob_example.py`: Playwright + Playwright browsers
113113
- `examples/pydoll_tab_example.py`: `pydoll-python` (Python 3.10+)
114114

115+
Multiregion support: see `examples/local_server_multiregion_browser_example.py`.
116+
115117
Run any example:
116118

117119
```bash
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
"""
2+
Example: local Stagehand server + multiregion Browserbase browser in eu-central-1.
3+
4+
What this demonstrates:
5+
- Run Stagehand server locally (SEA) with Browserbase cloud browser
6+
- Request a Browserbase session in eu-central-1
7+
- Attach Playwright to the same browser via CDP (`cdp_url`)
8+
- Stream SSE events by default for observe/act/extract/execute
9+
- Run the full flow: start → observe → act → extract → agent/execute → end
10+
11+
Environment variables required:
12+
- MODEL_API_KEY
13+
- BROWSERBASE_API_KEY
14+
- BROWSERBASE_PROJECT_ID
15+
16+
Optional:
17+
- STAGEHAND_BASE_URL (defaults to http://127.0.0.1:3000 when server="local")
18+
"""
19+
20+
from __future__ import annotations
21+
22+
import os
23+
import sys
24+
from typing import Any, Optional
25+
26+
from env import load_example_env
27+
28+
from stagehand import Stagehand
29+
30+
31+
def _print_stream_events(stream: Any, label: str) -> object | None:
32+
result_payload: object | None = None
33+
for event in stream:
34+
if event.type == "log":
35+
print(f"[{label}][log] {event.data.message}")
36+
continue
37+
38+
status = event.data.status
39+
print(f"[{label}][system] status={status}")
40+
if status == "finished":
41+
result_payload = event.data.result
42+
elif status == "error":
43+
error_message = event.data.error or "unknown error"
44+
raise RuntimeError(f"{label} stream reported error: {error_message}")
45+
46+
return result_payload
47+
48+
49+
def main() -> None:
50+
load_example_env()
51+
model_api_key = os.environ.get("MODEL_API_KEY")
52+
if not model_api_key:
53+
sys.exit("Set the MODEL_API_KEY environment variable to run this example.")
54+
55+
bb_api_key = os.environ.get("BROWSERBASE_API_KEY")
56+
bb_project_id = os.environ.get("BROWSERBASE_PROJECT_ID")
57+
if not bb_api_key or not bb_project_id:
58+
sys.exit(
59+
"Set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID to run this example."
60+
)
61+
62+
try:
63+
from playwright.sync_api import sync_playwright # type: ignore[import-not-found]
64+
except Exception:
65+
sys.exit(
66+
"Playwright is not installed. Install it with:\n"
67+
" uv pip install playwright\n"
68+
"and ensure browsers are installed (e.g. `playwright install chromium`)."
69+
)
70+
71+
session_id: Optional[str] = None
72+
73+
with Stagehand(
74+
server="local",
75+
browserbase_api_key=bb_api_key,
76+
browserbase_project_id=bb_project_id,
77+
model_api_key=model_api_key,
78+
local_openai_api_key=model_api_key,
79+
local_ready_timeout_s=30.0,
80+
) as client:
81+
print("⏳ Starting Stagehand session (local server + Browserbase browser)...")
82+
session = client.sessions.start(
83+
model_name="anthropic/claude-sonnet-4-6",
84+
browser={"type": "browserbase"},
85+
browserbase_session_create_params={"region": "eu-central-1"},
86+
verbose=2,
87+
)
88+
session_id = session.id
89+
90+
cdp_url = session.data.cdp_url
91+
if not cdp_url:
92+
sys.exit(
93+
"No cdp_url returned from the API for this session; cannot attach Playwright."
94+
)
95+
96+
print(f"✅ Session started: {session_id}")
97+
print("🔌 Connecting Playwright to the same browser over CDP...")
98+
99+
try:
100+
with sync_playwright() as p:
101+
browser = p.chromium.connect_over_cdp(cdp_url)
102+
try:
103+
context = browser.contexts[0] if browser.contexts else browser.new_context()
104+
page = context.pages[0] if context.pages else context.new_page()
105+
106+
page.goto("https://example.com", wait_until="domcontentloaded")
107+
108+
print("👀 Stagehand.observe(page=...) with SSE streaming...")
109+
observe_stream = session.observe(
110+
instruction="Find the most relevant click target on this page",
111+
page=page,
112+
stream_response=True,
113+
x_stream_response="true",
114+
)
115+
observe_result = _print_stream_events(observe_stream, "observe")
116+
117+
actions = observe_result or []
118+
if not actions:
119+
print("No actions found; ending session.")
120+
return
121+
122+
print("🖱️ Stagehand.act(page=...) with SSE streaming...")
123+
act_stream = session.act(
124+
input=actions[0],
125+
page=page,
126+
stream_response=True,
127+
x_stream_response="true",
128+
)
129+
_ = _print_stream_events(act_stream, "act")
130+
131+
print("🧠 Stagehand.extract(page=...) with SSE streaming...")
132+
extract_stream = session.extract(
133+
instruction="Extract the page title and the primary heading (h1) text",
134+
schema={
135+
"type": "object",
136+
"properties": {
137+
"title": {"type": "string"},
138+
"h1": {"type": "string"},
139+
},
140+
"required": ["title", "h1"],
141+
"additionalProperties": False,
142+
},
143+
page=page,
144+
stream_response=True,
145+
x_stream_response="true",
146+
)
147+
extracted = _print_stream_events(extract_stream, "extract")
148+
print("Extracted:", extracted)
149+
150+
print("🤖 Stagehand.execute(page=...) with SSE streaming...")
151+
execute_stream = session.execute(
152+
agent_config={"model": "anthropic/claude-opus-4-6"},
153+
execute_options={
154+
"instruction": (
155+
"Open the 'Learn more' link if present and summarize the destination in one sentence."
156+
),
157+
"max_steps": 5,
158+
},
159+
page=page,
160+
stream_response=True,
161+
x_stream_response="true",
162+
)
163+
execute_result = _print_stream_events(execute_stream, "execute")
164+
print("Execute result:", execute_result)
165+
finally:
166+
browser.close()
167+
finally:
168+
session.end()
169+
print("✅ Session ended.")
170+
171+
172+
if __name__ == "__main__":
173+
main()

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)