Skip to content

Commit 1e8c7c8

Browse files
Merge branch 'main' into fm/stg-474-more-tests
merge main
2 parents e3ebdac + 4986c27 commit 1e8c7c8

File tree

4 files changed

+37
-62
lines changed

4 files changed

+37
-62
lines changed

stagehand/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .agent import Agent
2-
from .client import Stagehand
32
from .config import StagehandConfig, default_config
43
from .handlers.observe_handler import ObserveHandler
4+
from .main import Stagehand
55
from .metrics import StagehandFunctionName, StagehandMetrics
66
from .page import StagehandPage
77
from .schemas import (

stagehand/api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ async def _create_session(self):
7373
"x-language": "python",
7474
}
7575

76-
client = self.httpx_client or httpx.AsyncClient(timeout=self.timeout_settings)
76+
client = httpx.AsyncClient(timeout=self.timeout_settings)
7777
async with client:
7878
resp = await client.post(
7979
f"{self.api_url}/sessions/start",
@@ -109,7 +109,7 @@ async def _execute(self, method: str, payload: dict[str, Any]) -> Any:
109109
# Convert snake_case keys to camelCase for the API
110110
modified_payload = convert_dict_keys_to_camel_case(payload)
111111

112-
client = self.httpx_client or httpx.AsyncClient(timeout=self.timeout_settings)
112+
client = httpx.AsyncClient(timeout=self.timeout_settings)
113113

114114
async with client:
115115
try:

stagehand/config.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Callable, Optional
1+
from typing import Any, Callable, Literal, Optional
22

33
from browserbase.types import SessionCreateParams as BrowserbaseSessionCreateParams
44
from pydantic import BaseModel, ConfigDict, Field
@@ -12,37 +12,49 @@ class StagehandConfig(BaseModel):
1212
1313
Attributes:
1414
env (str): Environment type. 'BROWSERBASE' for remote usage
15-
api_key (Optional[str]): API key for authentication.
16-
project_id (Optional[str]): Project identifier.
17-
headless (bool): Run browser in headless mode.
18-
logger (Optional[Callable[[Any], None]]): Custom logging function.
19-
dom_settle_timeout_ms (Optional[int]): Timeout for DOM to settle (in milliseconds).
15+
api_key (Optional[str]): BrowserbaseAPI key for authentication.
16+
project_id (Optional[str]): Browserbase Project identifier.
17+
api_url (Optional[str]): Stagehand API URL.
2018
browserbase_session_create_params (Optional[BrowserbaseSessionCreateParams]): Browserbase session create params.
21-
enable_caching (Optional[bool]): Enable caching functionality.
2219
browserbase_session_id (Optional[str]): Session ID for resuming Browserbase sessions.
2320
model_name (Optional[str]): Name of the model to use.
21+
model_api_key (Optional[str]): Model API key.
22+
logger (Optional[Callable[[Any], None]]): Custom logging function.
23+
verbose (Optional[int]): Verbosity level for logs (1=minimal, 2=medium, 3=detailed).
24+
use_rich_logging (bool): Whether to use Rich for colorized logging.
25+
dom_settle_timeout_ms (Optional[int]): Timeout for DOM to settle (in milliseconds).
26+
enable_caching (Optional[bool]): Enable caching functionality.
2427
self_heal (Optional[bool]): Enable self-healing functionality.
2528
wait_for_captcha_solves (Optional[bool]): Whether to wait for CAPTCHA to be solved.
2629
act_timeout_ms (Optional[int]): Timeout for act commands (in milliseconds).
30+
headless (bool): Run browser in headless mode
2731
system_prompt (Optional[str]): System prompt to use for LLM interactions.
28-
verbose (Optional[int]): Verbosity level for logs (1=minimal, 2=medium, 3=detailed).
2932
local_browser_launch_options (Optional[dict[str, Any]]): Local browser launch options.
3033
"""
3134

32-
env: str = "BROWSERBASE"
35+
env: Literal["BROWSERBASE", "LOCAL"] = "BROWSERBASE"
3336
api_key: Optional[str] = Field(
3437
None, alias="apiKey", description="Browserbase API key for authentication"
3538
)
3639
project_id: Optional[str] = Field(
3740
None, alias="projectId", description="Browserbase project ID"
3841
)
42+
api_url: Optional[str] = Field(
43+
None, alias="apiUrl", description="Stagehand API URL"
44+
) # might add a default value here
45+
model_api_key: Optional[str] = Field(
46+
None, alias="modelApiKey", description="Model API key"
47+
)
3948
verbose: Optional[int] = Field(
4049
1,
41-
description="Verbosity level for logs: 1=minimal (INFO), 2=medium (WARNING), 3=detailed (DEBUG)",
50+
description="Verbosity level for logs: 0=minimal (ERROR), 1=medium (INFO), 2=detailed (DEBUG)",
4251
)
4352
logger: Optional[Callable[[Any], None]] = Field(
4453
None, description="Custom logging function"
4554
)
55+
use_rich_logging: Optional[bool] = Field(
56+
True, description="Whether to use Rich for colorized logging"
57+
)
4658
dom_settle_timeout_ms: Optional[int] = Field(
4759
3000,
4860
alias="domSettleTimeoutMs",
Lines changed: 12 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import sys
55
import time
66
from pathlib import Path
7-
from typing import Any, Literal, Optional
7+
from typing import Any, Optional
88

99
import httpx
1010
from dotenv import load_dotenv
@@ -39,62 +39,27 @@
3939

4040
class Stagehand:
4141
"""
42-
Python client for interacting with a running Stagehand server and Browserbase remote headless browser.
43-
44-
Now supports automatically creating a new session if no session_id is provided.
45-
You can provide a configuration via the 'config' parameter, or use individual parameters to override
46-
the default configuration values.
42+
Main Stagehand class.
4743
"""
4844

49-
# Dictionary to store one lock per session_id
5045
_session_locks = {}
51-
52-
# Flag to track if cleanup has been called
5346
_cleanup_called = False
5447

5548
def __init__(
5649
self,
57-
config: Optional[StagehandConfig] = None,
58-
*,
59-
api_url: Optional[str] = None,
60-
model_api_key: Optional[str] = None,
61-
session_id: Optional[str] = None,
62-
env: Optional[Literal["BROWSERBASE", "LOCAL"]] = None,
63-
httpx_client: Optional[httpx.AsyncClient] = None,
64-
timeout_settings: Optional[httpx.Timeout] = None,
65-
use_rich_logging: bool = True,
50+
config: StagehandConfig = default_config,
6651
**config_overrides,
6752
):
6853
"""
6954
Initialize the Stagehand client.
7055
7156
Args:
7257
config (Optional[StagehandConfig]): Configuration object. If not provided, uses default_config.
73-
api_url (Optional[str]): The running Stagehand server URL. Overrides config if provided.
74-
model_api_key (Optional[str]): Your model API key (e.g. OpenAI, Anthropic, etc.). Overrides config if provided.
75-
session_id (Optional[str]): Existing Browserbase session ID to connect to. Overrides config if provided.
76-
env (Optional[Literal["BROWSERBASE", "LOCAL"]]): Environment to run in. Overrides config if provided.
77-
httpx_client (Optional[httpx.AsyncClient]): Optional custom httpx.AsyncClient instance.
78-
timeout_settings (Optional[httpx.Timeout]): Optional custom timeout settings for httpx.
79-
use_rich_logging (bool): Whether to use Rich for colorized logging.
8058
**config_overrides: Additional configuration overrides to apply to the config.
8159
"""
82-
# Start with provided config or default config
83-
if config is None:
84-
config = default_config
8560

8661
# Apply any overrides
8762
overrides = {}
88-
if api_url is not None:
89-
# api_url isn't in config, handle separately
90-
pass
91-
if model_api_key is not None:
92-
# model_api_key isn't in config, handle separately
93-
pass
94-
if session_id is not None:
95-
overrides["browserbase_session_id"] = session_id
96-
if env is not None:
97-
overrides["env"] = env
9863

9964
# Add any additional config overrides
10065
overrides.update(config_overrides)
@@ -106,8 +71,9 @@ def __init__(
10671
self.config = config
10772

10873
# Handle non-config parameters
109-
self.api_url = api_url or os.getenv("STAGEHAND_API_URL")
110-
self.model_api_key = model_api_key or os.getenv("MODEL_API_KEY")
74+
self.api_url = self.config.api_url or os.getenv("STAGEHAND_API_URL")
75+
self.model_api_key = self.config.model_api_key or os.getenv("MODEL_API_KEY")
76+
self.model_name = self.config.model_name
11177

11278
# Extract frequently used values from config for convenience
11379
self.browserbase_api_key = self.config.api_key or os.getenv(
@@ -117,7 +83,6 @@ def __init__(
11783
"BROWSERBASE_PROJECT_ID"
11884
)
11985
self.session_id = self.config.browserbase_session_id
120-
self.model_name = self.config.model_name
12186
self.dom_settle_timeout_ms = self.config.dom_settle_timeout_ms
12287
self.self_heal = self.config.self_heal
12388
self.wait_for_captcha_solves = self.config.wait_for_captcha_solves
@@ -161,8 +126,7 @@ def __init__(
161126
# Handle streaming response setting
162127
self.streamed_response = True
163128

164-
self.httpx_client = httpx_client
165-
self.timeout_settings = timeout_settings or httpx.Timeout(
129+
self.timeout_settings = httpx.Timeout(
166130
connect=180.0,
167131
read=180.0,
168132
write=180.0,
@@ -184,7 +148,9 @@ def __init__(
184148
# Initialize the centralized logger with the specified verbosity
185149
self.on_log = self.config.logger or default_log_handler
186150
self.logger = StagehandLogger(
187-
verbose=self.verbose, external_logger=self.on_log, use_rich=use_rich_logging
151+
verbose=self.verbose,
152+
external_logger=self.on_log,
153+
use_rich=self.config.use_rich_logging,
188154
)
189155

190156
# If using BROWSERBASE, session_id or creation params are needed
@@ -456,9 +422,7 @@ async def init(self):
456422

457423
if self.env == "BROWSERBASE":
458424
if not self._client:
459-
self._client = self.httpx_client or httpx.AsyncClient(
460-
timeout=self.timeout_settings
461-
)
425+
self._client = httpx.AsyncClient(timeout=self.timeout_settings)
462426

463427
# Create session if we don't have one
464428
if not self.session_id:
@@ -570,8 +534,7 @@ async def close(self):
570534
"Cannot end server session: HTTP client not available."
571535
)
572536

573-
# Close internal HTTPX client if it was created by Stagehand
574-
if self._client and not self.httpx_client:
537+
if self._client:
575538
self.logger.debug("Closing the internal HTTPX client...")
576539
await self._client.aclose()
577540
self._client = None

0 commit comments

Comments
 (0)