Skip to content

Commit c329e61

Browse files
committed
browserbase headers not required when server=local and browser=local
1 parent 077035e commit c329e61

File tree

4 files changed

+76
-26
lines changed

4 files changed

+76
-26
lines changed

src/stagehand/_client.py

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -106,18 +106,10 @@ def __init__(
106106

107107
if browserbase_api_key is None:
108108
browserbase_api_key = os.environ.get("BROWSERBASE_API_KEY")
109-
if browserbase_api_key is None:
110-
raise StagehandError(
111-
"The browserbase_api_key client option must be set either by passing browserbase_api_key to the client or by setting the BROWSERBASE_API_KEY environment variable"
112-
)
113-
self.browserbase_api_key = browserbase_api_key
114-
115109
if browserbase_project_id is None:
116110
browserbase_project_id = os.environ.get("BROWSERBASE_PROJECT_ID")
117-
if browserbase_project_id is None:
118-
raise StagehandError(
119-
"The browserbase_project_id client option must be set either by passing browserbase_project_id to the client or by setting the BROWSERBASE_PROJECT_ID environment variable"
120-
)
111+
112+
self.browserbase_api_key = browserbase_api_key
121113
self.browserbase_project_id = browserbase_project_id
122114

123115
if model_api_key is None:
@@ -206,12 +198,12 @@ def auth_headers(self) -> dict[str, str]:
206198
@property
207199
def _bb_api_key_auth(self) -> dict[str, str]:
208200
browserbase_api_key = self.browserbase_api_key
209-
return {"x-bb-api-key": browserbase_api_key}
201+
return {"x-bb-api-key": browserbase_api_key} if browserbase_api_key else {}
210202

211203
@property
212204
def _bb_project_id_auth(self) -> dict[str, str]:
213205
browserbase_project_id = self.browserbase_project_id
214-
return {"x-bb-project-id": browserbase_project_id}
206+
return {"x-bb-project-id": browserbase_project_id} if browserbase_project_id else {}
215207

216208
@property
217209
def _llm_model_api_key_auth(self) -> dict[str, str]:
@@ -398,18 +390,10 @@ def __init__(
398390

399391
if browserbase_api_key is None:
400392
browserbase_api_key = os.environ.get("BROWSERBASE_API_KEY")
401-
if browserbase_api_key is None:
402-
raise StagehandError(
403-
"The browserbase_api_key client option must be set either by passing browserbase_api_key to the client or by setting the BROWSERBASE_API_KEY environment variable"
404-
)
405-
self.browserbase_api_key = browserbase_api_key
406-
407393
if browserbase_project_id is None:
408394
browserbase_project_id = os.environ.get("BROWSERBASE_PROJECT_ID")
409-
if browserbase_project_id is None:
410-
raise StagehandError(
411-
"The browserbase_project_id client option must be set either by passing browserbase_project_id to the client or by setting the BROWSERBASE_PROJECT_ID environment variable"
412-
)
395+
396+
self.browserbase_api_key = browserbase_api_key
413397
self.browserbase_project_id = browserbase_project_id
414398

415399
if model_api_key is None:
@@ -497,12 +481,12 @@ def auth_headers(self) -> dict[str, str]:
497481
@property
498482
def _bb_api_key_auth(self) -> dict[str, str]:
499483
browserbase_api_key = self.browserbase_api_key
500-
return {"x-bb-api-key": browserbase_api_key}
484+
return {"x-bb-api-key": browserbase_api_key} if browserbase_api_key else {}
501485

502486
@property
503487
def _bb_project_id_auth(self) -> dict[str, str]:
504488
browserbase_project_id = self.browserbase_project_id
505-
return {"x-bb-project-id": browserbase_project_id}
489+
return {"x-bb-project-id": browserbase_project_id} if browserbase_project_id else {}
506490

507491
@property
508492
def _llm_model_api_key_auth(self) -> dict[str, str]:

src/stagehand/resources/sessions.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from typing import Dict, Union
5+
from typing import Dict, TYPE_CHECKING, Union
66
from datetime import datetime, timezone
77
from typing_extensions import Literal, overload
88

@@ -16,6 +16,9 @@
1616
session_observe_params,
1717
session_navigate_params,
1818
)
19+
20+
if TYPE_CHECKING:
21+
from .._client import Stagehand, AsyncStagehand
1922
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
2023
from .._utils import is_given, required_args, maybe_transform, strip_not_given, async_maybe_transform
2124
from .._compat import cached_property
@@ -36,6 +39,7 @@
3639
from ..types.session_extract_response import SessionExtractResponse
3740
from ..types.session_observe_response import SessionObserveResponse
3841
from ..types.session_navigate_response import SessionNavigateResponse
42+
from .._exceptions import StagehandError
3943

4044
__all__ = ["SessionsResource", "AsyncSessionsResource"]
4145

@@ -51,6 +55,20 @@ def _format_x_sent_at(value: Union[str, datetime] | Omit) -> str | NotGiven:
5155
return value
5256

5357

58+
def _requires_browserbase_credentials(
59+
_: "Stagehand | AsyncStagehand",
60+
browser: session_start_params.Browser | Omit,
61+
) -> bool:
62+
if not is_given(browser):
63+
return True
64+
65+
browser_type = None
66+
if isinstance(browser, dict):
67+
browser_type = browser.get("type")
68+
69+
return browser_type != "local"
70+
71+
5472
class SessionsResource(SyncAPIResource):
5573
@cached_property
5674
def with_raw_response(self) -> SessionsResourceWithRawResponse:
@@ -986,6 +1004,16 @@ def start(
9861004
9871005
timeout: Override the client-level default timeout for this request, in seconds
9881006
"""
1007+
if _requires_browserbase_credentials(self._client, browser):
1008+
missing: list[str] = []
1009+
if not self._client.browserbase_api_key:
1010+
missing.append("browserbase_api_key")
1011+
if not self._client.browserbase_project_id:
1012+
missing.append("browserbase_project_id")
1013+
if missing:
1014+
raise StagehandError(
1015+
f"Browserbase credentials are required when launching a Browserbase browser: missing {', '.join(missing)}."
1016+
)
9891017
extra_headers = {
9901018
**strip_not_given(
9911019
{
@@ -1955,6 +1983,16 @@ async def start(
19551983
19561984
timeout: Override the client-level default timeout for this request, in seconds
19571985
"""
1986+
if _requires_browserbase_credentials(self._client, browser):
1987+
missing: list[str] = []
1988+
if not self._client.browserbase_api_key:
1989+
missing.append("browserbase_api_key")
1990+
if not self._client.browserbase_project_id:
1991+
missing.append("browserbase_project_id")
1992+
if missing:
1993+
raise StagehandError(
1994+
f"Browserbase credentials are required when launching a Browserbase browser: missing {', '.join(missing)}."
1995+
)
19581996
extra_headers = {
19591997
**strip_not_given(
19601998
{

tests/test_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ def test_validate_headers(self) -> None:
426426
model_api_key=None,
427427
_strict_response_validation=True,
428428
)
429-
_ = client2
429+
client2.sessions.start(model_name="openai/gpt-5-nano")
430430

431431
def test_default_query_option(self) -> None:
432432
client = Stagehand(

tests/test_local_server.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from respx import MockRouter
66

77
from stagehand import Stagehand, AsyncStagehand
8+
from stagehand._exceptions import StagehandError
89

910

1011
class _DummySeaServer:
@@ -96,3 +97,30 @@ async def test_async_local_mode_starts_before_first_request(
9697
assert dummy.started == 1
9798

9899
assert dummy.closed == 1
100+
101+
102+
def test_local_server_requires_browserbase_keys_for_browserbase_sessions(
103+
monkeypatch: pytest.MonkeyPatch,
104+
) -> None:
105+
_set_required_env(monkeypatch)
106+
monkeypatch.delenv("BROWSERBASE_API_KEY", raising=False)
107+
monkeypatch.delenv("BROWSERBASE_PROJECT_ID", raising=False)
108+
client = Stagehand(server="local")
109+
with pytest.raises(StagehandError):
110+
client.sessions.start(model_name="openai/gpt-5-nano")
111+
112+
113+
def test_local_server_allows_local_browser_without_browserbase_keys(
114+
monkeypatch: pytest.MonkeyPatch,
115+
) -> None:
116+
_set_required_env(monkeypatch)
117+
monkeypatch.delenv("BROWSERBASE_API_KEY", raising=False)
118+
monkeypatch.delenv("BROWSERBASE_PROJECT_ID", raising=False)
119+
client = Stagehand(server="local")
120+
client._post = lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("post called"))
121+
122+
with pytest.raises(RuntimeError, match="post called"):
123+
client.sessions.start(
124+
model_name="openai/gpt-5-nano",
125+
browser={"type": "local"},
126+
)

0 commit comments

Comments
 (0)