Skip to content

Commit 72edd31

Browse files
committed
add comments
1 parent b663255 commit 72edd31

File tree

6 files changed

+71
-0
lines changed

6 files changed

+71
-0
lines changed

src/stagehand/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,13 @@
3737
APIResponseValidationError,
3838
)
3939
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
40+
41+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
4042
from ._utils._logs import setup_logging as _setup_logging
4143
from ._custom.session import Session, AsyncSession
4244

45+
### </END CUSTOM CODE>
46+
4347
__all__ = [
4448
"types",
4549
"__version__",
@@ -74,8 +78,10 @@
7478
"AsyncStream",
7579
"Stagehand",
7680
"AsyncStagehand",
81+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
7782
"Session",
7883
"AsyncSession",
84+
### </END CUSTOM CODE>
7985
"file_from_path",
8086
"BaseModel",
8187
"DEFAULT_TIMEOUT",

src/stagehand/_client.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
SyncAPIClient,
3131
AsyncAPIClient,
3232
)
33+
34+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
3335
from ._custom.session import install_stainless_session_patches
3436
from ._custom.sea_server import (
3537
copy_local_mode_kwargs,
@@ -40,9 +42,14 @@
4042
prepare_async_client_base_url,
4143
)
4244

45+
### </END CUSTOM CODE>
46+
4347
if TYPE_CHECKING:
4448
from .resources import sessions
49+
50+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
4551
from ._custom.sea_server import SeaServerManager
52+
### </END CUSTOM CODE>
4653

4754
__all__ = [
4855
"Timeout",
@@ -55,14 +62,17 @@
5562
"AsyncClient",
5663
]
5764

65+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
5866
install_stainless_session_patches()
67+
### </END CUSTOM CODE>
5968

6069

6170
class Stagehand(SyncAPIClient):
6271
# client options
6372
browserbase_api_key: str | None
6473
browserbase_project_id: str | None
6574
model_api_key: str | None
75+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
6676
_server_mode: Literal["remote", "local"]
6777
_local_stagehand_binary_path: str | os.PathLike[str] | None
6878
_local_host: str
@@ -72,6 +82,7 @@ class Stagehand(SyncAPIClient):
7282
_local_ready_timeout_s: float
7383
_local_shutdown_on_close: bool
7484
_sea_server: SeaServerManager | None
85+
### </END CUSTOM CODE>
7586

7687
def __init__(
7788
self,
@@ -126,6 +137,7 @@ def __init__(
126137

127138
self.model_api_key = model_api_key
128139

140+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
129141
base_url = configure_client_base_url(
130142
self,
131143
server=server,
@@ -139,6 +151,7 @@ def __init__(
139151
base_url=base_url,
140152
model_api_key=model_api_key,
141153
)
154+
### </END CUSTOM CODE>
142155

143156
super().__init__(
144157
version=__version__,
@@ -155,23 +168,29 @@ def __init__(
155168

156169
@override
157170
def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
171+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
158172
local_base_url = prepare_sync_client_base_url(self)
159173
if local_base_url is not None:
160174
self.base_url = local_base_url
175+
### </END CUSTOM CODE>
161176
return super()._prepare_options(options)
162177

163178
@override
164179
def close(self) -> None:
165180
try:
166181
super().close()
167182
finally:
183+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
168184
close_sync_client_sea_server(self)
185+
### </END CUSTOM CODE>
169186

187+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
170188
@cached_property
171189
def sessions(self) -> sessions.SessionsResource:
172190
from .resources.sessions import SessionsResource
173191

174192
return SessionsResource(self)
193+
### </END CUSTOM CODE>
175194

176195
@cached_property
177196
def with_raw_response(self) -> StagehandWithRawResponse:
@@ -273,6 +292,7 @@ def copy(
273292
max_retries=max_retries if is_given(max_retries) else self.max_retries,
274293
default_headers=headers,
275294
default_query=params,
295+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
276296
**copy_local_mode_kwargs(
277297
self,
278298
server=server,
@@ -284,6 +304,7 @@ def copy(
284304
local_ready_timeout_s=local_ready_timeout_s,
285305
local_shutdown_on_close=local_shutdown_on_close,
286306
),
307+
### </END CUSTOM CODE>
287308
**_extra_kwargs,
288309
)
289310

@@ -330,6 +351,7 @@ class AsyncStagehand(AsyncAPIClient):
330351
browserbase_api_key: str | None
331352
browserbase_project_id: str | None
332353
model_api_key: str | None
354+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
333355
_server_mode: Literal["remote", "local"]
334356
_local_stagehand_binary_path: str | os.PathLike[str] | None
335357
_local_host: str
@@ -339,6 +361,7 @@ class AsyncStagehand(AsyncAPIClient):
339361
_local_ready_timeout_s: float
340362
_local_shutdown_on_close: bool
341363
_sea_server: SeaServerManager | None
364+
### </END CUSTOM CODE>
342365

343366
def __init__(
344367
self,
@@ -393,6 +416,7 @@ def __init__(
393416

394417
self.model_api_key = model_api_key
395418

419+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
396420
base_url = configure_client_base_url(
397421
self,
398422
server=server,
@@ -406,6 +430,7 @@ def __init__(
406430
base_url=base_url,
407431
model_api_key=model_api_key,
408432
)
433+
### </END CUSTOM CODE>
409434

410435
super().__init__(
411436
version=__version__,
@@ -422,23 +447,29 @@ def __init__(
422447

423448
@override
424449
async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
450+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
425451
local_base_url = await prepare_async_client_base_url(self)
426452
if local_base_url is not None:
427453
self.base_url = local_base_url
454+
### </END CUSTOM CODE>
428455
return await super()._prepare_options(options)
429456

430457
@override
431458
async def close(self) -> None:
432459
try:
433460
await super().close()
434461
finally:
462+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
435463
await close_async_client_sea_server(self)
464+
### </END CUSTOM CODE>
436465

466+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
437467
@cached_property
438468
def sessions(self) -> sessions.AsyncSessionsResource:
439469
from .resources.sessions import AsyncSessionsResource
440470

441471
return AsyncSessionsResource(self)
472+
### </END CUSTOM CODE>
442473

443474
@cached_property
444475
def with_raw_response(self) -> AsyncStagehandWithRawResponse:
@@ -540,6 +571,7 @@ def copy(
540571
max_retries=max_retries if is_given(max_retries) else self.max_retries,
541572
default_headers=headers,
542573
default_query=params,
574+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
543575
**copy_local_mode_kwargs(
544576
self,
545577
server=server,
@@ -551,6 +583,7 @@ def copy(
551583
local_ready_timeout_s=local_ready_timeout_s,
552584
local_shutdown_on_close=local_shutdown_on_close,
553585
),
586+
### </END CUSTOM CODE>
554587
**_extra_kwargs,
555588
)
556589

src/stagehand/_custom/sea_binary.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def default_binary_filename() -> str:
2525

2626

2727
def _cache_dir() -> Path:
28+
# Avoid extra deps (e.g. platformdirs) for now.
2829
if sys.platform == "darwin":
2930
root = Path.home() / "Library" / "Caches"
3031
elif sys.platform.startswith("win"):
@@ -43,6 +44,7 @@ def _ensure_executable(path: Path) -> None:
4344

4445

4546
def _resource_binary_path(filename: str) -> Path | None:
47+
# Expect binaries to live at stagehand/_sea/<filename> inside the installed package.
4648
try:
4749
root = importlib_resources.files("stagehand")
4850
except Exception:
@@ -93,12 +95,16 @@ def resolve_binary_path(
9395
return path
9496

9597
filename = default_binary_filename()
98+
99+
# Prefer packaged resources (works for wheel installs).
96100
resource_path = _resource_binary_path(filename)
97101
if resource_path is not None:
102+
# Best-effort versioning to keep cached binaries stable across upgrades.
98103
if version is None:
99104
version = os.environ.get("STAGEHAND_VERSION") or __version__
100105
return _copy_to_cache(src=resource_path, filename=filename, version=version)
101106

107+
# Fallback: source checkout layout (works for local dev in-repo).
102108
here = Path(__file__).resolve()
103109
repo_root = here.parents[3]
104110
candidate = repo_root / "bin" / "sea" / filename

src/stagehand/_custom/sea_server.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ def _wait_ready_sync(*, base_url: str, timeout_s: float) -> None:
9494
with httpx.Client(timeout=1.0) as client:
9595
while time.monotonic() < deadline:
9696
try:
97+
# stagehand-binary: /health
98+
# stagehand/packages/server: /readyz and /healthz
9799
for path in ("/readyz", "/healthz", "/health"):
98100
resp = client.get(f"{base_url}{path}")
99101
if resp.status_code == 200:
@@ -145,11 +147,18 @@ def base_url(self) -> str | None:
145147

146148
def _build_process_env(self, *, port: int) -> dict[str, str]:
147149
proc_env = dict(os.environ)
150+
# Force production mode so inherited NODE_ENV=development never reaches the
151+
# SEA child process. Development mode breaks under SEA because pino-pretty
152+
# is an optional dependency that is not present in the packaged binary.
148153
proc_env["NODE_ENV"] = "production"
154+
# Server package expects BB_ENV to be set (see packages/server/src/lib/env.ts)
149155
proc_env.setdefault("BB_ENV", "local")
150156
proc_env["HOST"] = self._config.host
151157
proc_env["PORT"] = str(port)
152158
proc_env["HEADLESS"] = "true" if self._config.headless else "false"
159+
# Always set MODEL_API_KEY in the child env so the SDK constructor value wins
160+
# over any inherited parent MODEL_API_KEY. An empty string preserves the
161+
# "explicitly unset" case instead of silently reusing the parent's value.
153162
proc_env["MODEL_API_KEY"] = self._config.model_api_key or ""
154163
if self._config.chrome_path:
155164
proc_env["CHROME_PATH"] = self._config.chrome_path

src/stagehand/_custom/session.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ class Session(SessionStartResponse):
177177
"""A Stagehand session bound to a specific `session_id`."""
178178

179179
def __init__(self, client: Stagehand, id: str, data: SessionStartResponseData, success: bool) -> None:
180+
# Must call super().__init__() first to initialize Pydantic's __pydantic_extra__
181+
# before setting attributes.
180182
super().__init__(data=data, success=success)
181183
self._client = client
182184
self.id = id
@@ -324,6 +326,8 @@ class AsyncSession(SessionStartResponse):
324326
"""Async variant of `Session`."""
325327

326328
def __init__(self, client: AsyncStagehand, id: str, data: SessionStartResponseData, success: bool) -> None:
329+
# Must call super().__init__() first to initialize Pydantic's __pydantic_extra__
330+
# before setting attributes.
327331
super().__init__(data=data, success=success)
328332
self._client = client
329333
self.id = id

src/stagehand/resources/sessions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
from __future__ import annotations
44

5+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
56
from typing import TYPE_CHECKING, Dict, Optional, cast
7+
8+
### </END CUSTOM CODE>
69
from typing_extensions import Literal, overload
710

811
import httpx
@@ -37,8 +40,10 @@
3740
from ..types.session_observe_response import SessionObserveResponse
3841
from ..types.session_navigate_response import SessionNavigateResponse
3942

43+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
4044
if TYPE_CHECKING:
4145
from .. import Session, AsyncSession
46+
### </END CUSTOM CODE>
4247

4348
__all__ = ["SessionsResource", "AsyncSessionsResource"]
4449

@@ -931,7 +936,9 @@ def start(
931936
extra_query: Query | None = None,
932937
extra_body: Body | None = None,
933938
timeout: float | httpx.Timeout | None | NotGiven = not_given,
939+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
934940
) -> Session:
941+
### </END CUSTOM CODE>
935942
"""Creates a new browser session with the specified configuration.
936943
937944
Returns a
@@ -970,6 +977,7 @@ def start(
970977
),
971978
**(extra_headers or {}),
972979
}
980+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
973981
return cast(
974982
"Session",
975983
self._post(
@@ -996,6 +1004,7 @@ def start(
9961004
cast_to=SessionStartResponse,
9971005
),
9981006
)
1007+
### </END CUSTOM CODE>
9991008

10001009

10011010
class AsyncSessionsResource(AsyncAPIResource):
@@ -1886,7 +1895,9 @@ async def start(
18861895
extra_query: Query | None = None,
18871896
extra_body: Body | None = None,
18881897
timeout: float | httpx.Timeout | None | NotGiven = not_given,
1898+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
18891899
) -> AsyncSession:
1900+
### </END CUSTOM CODE>
18901901
"""Creates a new browser session with the specified configuration.
18911902
18921903
Returns a
@@ -1925,6 +1936,7 @@ async def start(
19251936
),
19261937
**(extra_headers or {}),
19271938
}
1939+
### <CUSTOM CODE HANDWRITTEN BY STAGEHAND TEAM (not codegen)>
19281940
return cast(
19291941
"AsyncSession",
19301942
await self._post(
@@ -1951,6 +1963,7 @@ async def start(
19511963
cast_to=SessionStartResponse,
19521964
),
19531965
)
1966+
### </END CUSTOM CODE>
19541967

19551968

19561969
class SessionsResourceWithRawResponse:

0 commit comments

Comments
 (0)