stagehand 4.2.0a0.dev1582__tar.gz → 4.2.0a0.dev1584__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/PKG-INFO +2 -1
  2. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/pyproject.toml +2 -1
  3. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/__init__.py +2 -0
  4. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_extension/service-worker.js +14 -5
  5. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_generated/protocol_version.py +1 -1
  6. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/browser.py +24 -3
  7. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/browserbase_session.py +40 -18
  8. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/client_models.py +12 -0
  9. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/client_types.py +15 -0
  10. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/README.md +0 -0
  11. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_extension/blank.html +0 -0
  12. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_extension/content-script.js +0 -0
  13. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_extension/manifest.json +0 -0
  14. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_extension/offscreen/service-worker-heartbeat.html +0 -0
  15. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_extension/offscreen/service-worker-heartbeat.js +0 -0
  16. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_extension/wake-service-worker.html +0 -0
  17. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_extension/wake-service-worker.js +0 -0
  18. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_generated/__init__.py +0 -0
  19. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_generated/input_types.py +0 -0
  20. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_generated/models.py +0 -0
  21. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_sdk_identity.py +0 -0
  22. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/_validation.py +0 -0
  23. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/browser_clipboard.py +0 -0
  24. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/browser_context.py +0 -0
  25. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/browserbase_services.py +0 -0
  26. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/cdp_client.py +0 -0
  27. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/extension_assets.py +0 -0
  28. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/file_upload.py +0 -0
  29. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/locator.py +0 -0
  30. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/page.py +0 -0
  31. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/py.typed +0 -0
  32. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/response.py +0 -0
  33. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/rpc_client.py +0 -0
  34. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/stagehand.py +0 -0
  35. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/timeouts.py +0 -0
  36. {stagehand-4.2.0a0.dev1582 → stagehand-4.2.0a0.dev1584}/src/stagehand/webmcp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stagehand
3
- Version: 4.2.0a0.dev1582
3
+ Version: 4.2.0a0.dev1584
4
4
  Summary: Browserbase's SDK for building browser agents
5
5
  Keywords: ai,agent,ai-agent,browser,browser-agent,browser-automation,computer-use,headless,playwright,cdp,llm,automation,web-scraping,testing
6
6
  Author: Browserbase
@@ -18,6 +18,7 @@ Classifier: Topic :: Software Development :: Testing
18
18
  Classifier: Typing :: Typed
19
19
  Classifier: Framework :: AsyncIO
20
20
  Requires-Dist: browserbase>=1.15,<2
21
+ Requires-Dist: httpx>=0.23,<1
21
22
  Requires-Dist: opentelemetry-api>=1.35,<2
22
23
  Requires-Dist: pydantic>=2.12,<3
23
24
  Requires-Dist: typing-extensions>=4.12,<5
@@ -1,11 +1,12 @@
1
1
  [project]
2
2
  name = "stagehand"
3
- version = "4.2.0a0.dev1582"
3
+ version = "4.2.0a0.dev1584"
4
4
  description = "Browserbase's SDK for building browser agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  dependencies = [
8
8
  "browserbase>=1.15,<2",
9
+ "httpx>=0.23,<1",
9
10
  "opentelemetry-api>=1.35,<2",
10
11
  "pydantic>=2.12,<3",
11
12
  "typing_extensions>=4.12,<5",
@@ -68,6 +68,7 @@ from .client_models import (
68
68
  ExtractResult,
69
69
  )
70
70
  from .client_types import (
71
+ BrowserbaseClientOptions,
71
72
  CacheOptions,
72
73
  LLMGenerateCallback,
73
74
  LLMGenerateInput,
@@ -102,6 +103,7 @@ __all__ = [
102
103
  "BrowserClipboard",
103
104
  "BrowserContext",
104
105
  "BrowserbaseBrowserSettings",
106
+ "BrowserbaseClientOptions",
105
107
  "BrowserbaseFetchResult",
106
108
  "BrowserbaseProxyConfig",
107
109
  "BrowserbaseRegion",
@@ -7363,7 +7363,7 @@ function isRecord$1(value) {
7363
7363
  }
7364
7364
  var package_default$1 = {
7365
7365
  name: "@browserbasehq/stagehand-protocol",
7366
- version: "2.1.0-alpha-34be780da11ca89bd973e6d5d336be5b40b27b18",
7366
+ version: "2.1.0-alpha-100e201a019aa55fadfce36744f560b37b66b682",
7367
7367
  "private": true,
7368
7368
  type: "module",
7369
7369
  exports: {
@@ -12560,29 +12560,38 @@ strictObject({
12560
12560
  extensionId: string().min(1).optional()
12561
12561
  }).meta({ id: "LocalBrowserConnectOptions" });
12562
12562
  var DEFAULT_BROWSERBASE_URL = "https://api.browserbase.com";
12563
+ var BrowserbaseClientOptionsSchema = strictObject({
12564
+ timeout: int().nonnegative().optional(),
12565
+ maxRetries: int().nonnegative().optional(),
12566
+ defaultHeaders: record(string(), string().nullable().optional()).optional(),
12567
+ defaultQuery: record(string(), string().optional()).optional(),
12568
+ fetch: custom((value) => typeof value === "function", { message: "fetch must be a function" }).optional()
12569
+ });
12563
12570
  looseObject({
12564
12571
  apiKey: string().min(1),
12565
12572
  baseUrl: url().default(DEFAULT_BROWSERBASE_URL),
12573
+ clientOptions: BrowserbaseClientOptionsSchema.optional(),
12566
12574
  apiUrl: never().optional(),
12567
12575
  type: never().optional()
12568
12576
  }).meta({ id: "BrowserbaseLaunchOptions" });
12569
12577
  strictObject({
12570
12578
  apiKey: string().min(1),
12571
12579
  baseUrl: url().default(DEFAULT_BROWSERBASE_URL),
12580
+ clientOptions: BrowserbaseClientOptionsSchema.optional(),
12572
12581
  sessionId: string().min(1),
12573
12582
  extensionId: string().min(1).optional()
12574
12583
  }).meta({ id: "BrowserbaseConnectOptions" });
12575
- var BrowserbaseClientOptionsSchema = {
12584
+ var BrowserbaseCredentialsShape = {
12576
12585
  apiKey: string().min(1),
12577
12586
  baseUrl: url().default(DEFAULT_BROWSERBASE_URL)
12578
12587
  };
12579
12588
  strictObject({
12580
- ...BrowserbaseClientOptionsSchema,
12589
+ ...BrowserbaseCredentialsShape,
12581
12590
  query: string().min(1).max(200),
12582
12591
  numResults: int().min(1).max(25).optional()
12583
12592
  }).meta({ id: "BrowserbaseSearchOptions" });
12584
12593
  strictObject({
12585
- ...BrowserbaseClientOptionsSchema,
12594
+ ...BrowserbaseCredentialsShape,
12586
12595
  url: url(),
12587
12596
  allowInsecureSsl: boolean().optional(),
12588
12597
  allowRedirects: boolean().optional(),
@@ -54385,7 +54394,7 @@ var WebTracerProvider = class extends BasicTracerProvider {
54385
54394
  };
54386
54395
  var package_default = {
54387
54396
  name: "@browserbasehq/stagehand-extension",
54388
- version: "1.1.0-alpha-34be780da11ca89bd973e6d5d336be5b40b27b18",
54397
+ version: "1.1.0-alpha-100e201a019aa55fadfce36744f560b37b66b682",
54389
54398
  "private": true,
54390
54399
  type: "module",
54391
54400
  scripts: {
@@ -1,4 +1,4 @@
1
1
  """Generated from packages/protocol/package.json. Do not edit."""
2
2
 
3
3
  PROTOCOL_SEMVER_PATTERN = '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$'
4
- STAGEHAND_PROTOCOL_VERSION = '2.1.0-alpha-34be780da11ca89bd973e6d5d336be5b40b27b18'
4
+ STAGEHAND_PROTOCOL_VERSION = '2.1.0-alpha-100e201a019aa55fadfce36744f560b37b66b682'
@@ -30,6 +30,7 @@ from .browserbase_services import fetch_browserbase, search_browserbase
30
30
  from .browserbase_session import DEFAULT_BROWSERBASE_URL, _create_browserbase_session_client
31
31
  from .cdp_client import CDPClient, CDPConnectionClosedError
32
32
  from .client_models import (
33
+ BrowserbaseClientOptions,
33
34
  BrowserbaseConnectOptions,
34
35
  BrowserbaseFetchResult,
35
36
  BrowserbaseSearchResult,
@@ -40,6 +41,7 @@ from .client_models import (
40
41
  _BrowserbaseFetchOptions,
41
42
  _BrowserbaseSearchOptions,
42
43
  )
44
+ from .client_types import BrowserbaseClientOptions as BrowserbaseClientOptionsInput
43
45
  from .extension_assets import extension_directory
44
46
  from .timeouts import stagehand_init_deadline
45
47
 
@@ -558,6 +560,17 @@ class _ConnectedBrowserSource:
558
560
  return None
559
561
 
560
562
 
563
+ def _sdk_client_options(options: BrowserbaseClientOptions | None) -> dict[str, Any] | None:
564
+ if options is None:
565
+ return None
566
+ # Read attributes rather than model_dump so a caller's http_client passes through as-is.
567
+ return {
568
+ name: value
569
+ for name in type(options).model_fields
570
+ if (value := getattr(options, name)) is not None
571
+ }
572
+
573
+
561
574
  class BrowserbaseBrowser:
562
575
  @stagehand_init_deadline
563
576
  async def launch(
@@ -565,6 +578,7 @@ class BrowserbaseBrowser:
565
578
  *,
566
579
  api_key: str,
567
580
  base_url: str = DEFAULT_BROWSERBASE_URL,
581
+ client_options: BrowserbaseClientOptionsInput | None = None,
568
582
  browser_settings: BrowserbaseBrowserSettings | None = None,
569
583
  extension_id: str | None = None,
570
584
  keep_alive: bool | None = None,
@@ -598,9 +612,14 @@ class BrowserbaseBrowser:
598
612
  and not options.browser_settings.extension_id.strip()
599
613
  ):
600
614
  raise ValueError("browser_settings.extension_id must not be empty")
601
- session = await _create_browserbase_session_client(api_key, base_url).create_session(
602
- options
615
+ validated_client_options = (
616
+ BrowserbaseClientOptions.model_validate(client_options)
617
+ if client_options is not None
618
+ else None
603
619
  )
620
+ session = await _create_browserbase_session_client(
621
+ api_key, base_url, _sdk_client_options(validated_client_options)
622
+ ).create_session(options)
604
623
  source = ResolvedBrowserSource(
605
624
  cdp_url=session.cdp_url,
606
625
  keep_alive=options.keep_alive or False,
@@ -623,6 +642,7 @@ class BrowserbaseBrowser:
623
642
  *,
624
643
  api_key: str,
625
644
  base_url: str = DEFAULT_BROWSERBASE_URL,
645
+ client_options: BrowserbaseClientOptionsInput | None = None,
626
646
  session_id: str,
627
647
  extension_id: str | None = None,
628
648
  ) -> StagehandBrowser:
@@ -631,13 +651,14 @@ class BrowserbaseBrowser:
631
651
  for name, value in (
632
652
  ("api_key", api_key),
633
653
  ("base_url", base_url),
654
+ ("client_options", client_options),
634
655
  ("session_id", session_id),
635
656
  ("extension_id", extension_id),
636
657
  )
637
658
  if value is not None
638
659
  })
639
660
  connection = await _create_browserbase_session_client(
640
- options.api_key, options.base_url
661
+ options.api_key, options.base_url, _sdk_client_options(options.client_options)
641
662
  ).connect_session(options.session_id)
642
663
  return await _connect_browser(
643
664
  provider="browserbase",
@@ -1,14 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- from collections.abc import Awaitable, Callable, Mapping
4
+ from collections.abc import AsyncIterator, Awaitable, Callable, Mapping
5
+ from contextlib import asynccontextmanager
5
6
  from dataclasses import dataclass, field
6
- from typing import Any, Protocol
7
+ from typing import TYPE_CHECKING, Any, Protocol
7
8
 
8
9
  from ._generated.models import BrowserbaseRegion, BrowserbaseSessionCreateParams
9
10
  from ._sdk_identity import STAGEHAND_SESSION_METADATA
10
11
  from .extension_assets import build_extension_archive
11
12
 
13
+ if TYPE_CHECKING:
14
+ from browserbase import AsyncBrowserbase
15
+
12
16
  DEFAULT_BROWSERBASE_URL = "https://api.browserbase.com"
13
17
 
14
18
 
@@ -81,21 +85,41 @@ class _BrowserbaseAPI(Protocol):
81
85
 
82
86
 
83
87
  class _OfficialBrowserbaseAPI:
84
- def __init__(self, api_key: str, base_url: str) -> None:
88
+ def __init__(
89
+ self,
90
+ api_key: str,
91
+ base_url: str,
92
+ client_options: Mapping[str, Any] | None = None,
93
+ ) -> None:
85
94
  self._api_key = api_key
86
95
  self._base_url = base_url
96
+ self._client_options = dict(client_options or {})
87
97
 
88
- async def upload_extension(self, archive: bytes) -> str:
98
+ @asynccontextmanager
99
+ async def _client(self) -> AsyncIterator[AsyncBrowserbase]:
89
100
  from browserbase import AsyncBrowserbase
90
101
 
91
- async with AsyncBrowserbase(api_key=self._api_key, base_url=self._base_url) as client:
102
+ client = AsyncBrowserbase(
103
+ **self._client_options,
104
+ api_key=self._api_key,
105
+ base_url=self._base_url,
106
+ )
107
+ if self._client_options.get("http_client") is not None:
108
+ # Closing AsyncBrowserbase closes its HTTP client, which the caller owns.
109
+ yield client
110
+ return
111
+ async with client:
112
+ yield client
113
+
114
+ async def upload_extension(self, archive: bytes) -> str:
115
+ async with self._client() as client:
92
116
  extension = await client.extensions.create(file=("stagehand-extension.zip", archive))
93
117
  return extension.id
94
118
 
95
119
  async def delete_extension(self, extension_id: str) -> None:
96
- from browserbase import AsyncBrowserbase, omit
120
+ from browserbase import omit
97
121
 
98
- async with AsyncBrowserbase(api_key=self._api_key, base_url=self._base_url) as client:
122
+ async with self._client() as client:
99
123
  await client.extensions.delete(extension_id, extra_headers={"Content-Type": omit})
100
124
 
101
125
  async def create_session(
@@ -105,14 +129,12 @@ class _OfficialBrowserbaseAPI:
105
129
  user_metadata: Mapping[str, Any],
106
130
  extension_id: str | None,
107
131
  ) -> tuple[str, str]:
108
- from browserbase import AsyncBrowserbase
109
-
110
132
  kwargs = _session_create_kwargs(
111
133
  options,
112
134
  user_metadata=user_metadata,
113
135
  extension_id=extension_id,
114
136
  )
115
- async with AsyncBrowserbase(api_key=self._api_key, base_url=self._base_url) as client:
137
+ async with self._client() as client:
116
138
  session = await client.sessions.create(**kwargs)
117
139
  return session.id, session.connect_url
118
140
 
@@ -120,9 +142,7 @@ class _OfficialBrowserbaseAPI:
120
142
  self,
121
143
  session_id: str,
122
144
  ) -> tuple[str, str | None, BrowserbaseRegion | None]:
123
- from browserbase import AsyncBrowserbase
124
-
125
- async with AsyncBrowserbase(api_key=self._api_key, base_url=self._base_url) as client:
145
+ async with self._client() as client:
126
146
  session = await client.sessions.retrieve(session_id)
127
147
  return (
128
148
  session.id,
@@ -131,9 +151,7 @@ class _OfficialBrowserbaseAPI:
131
151
  )
132
152
 
133
153
  async def release_session(self, session_id: str) -> None:
134
- from browserbase import AsyncBrowserbase
135
-
136
- async with AsyncBrowserbase(api_key=self._api_key, base_url=self._base_url) as client:
154
+ async with self._client() as client:
137
155
  await client.sessions.update(session_id, status="REQUEST_RELEASE")
138
156
 
139
157
 
@@ -298,5 +316,9 @@ class _BrowserbaseSessionClient:
298
316
  await self._delete_extension_best_effort(extension_id)
299
317
 
300
318
 
301
- def _create_browserbase_session_client(api_key: str, base_url: str) -> _BrowserbaseSessionClient:
302
- return _BrowserbaseSessionClient(_OfficialBrowserbaseAPI(api_key, base_url))
319
+ def _create_browserbase_session_client(
320
+ api_key: str,
321
+ base_url: str,
322
+ client_options: Mapping[str, Any] | None = None,
323
+ ) -> _BrowserbaseSessionClient:
324
+ return _BrowserbaseSessionClient(_OfficialBrowserbaseAPI(api_key, base_url, client_options))
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from collections.abc import Awaitable, Callable
4
4
  from typing import Annotated, Any, Generic, Literal, TypeVar
5
5
 
6
+ import httpx
6
7
  from pydantic import BaseModel, ConfigDict, Field, model_validator
7
8
 
8
9
  from ._generated import models as _models
@@ -107,11 +108,22 @@ class LocalBrowserConnectOptions(WireModel):
107
108
  extension_id: Annotated[str | None, Field(min_length=1)] = None
108
109
 
109
110
 
111
+ class BrowserbaseClientOptions(WireModel):
112
+ model_config = ConfigDict(extra="forbid", strict=True, arbitrary_types_allowed=True)
113
+
114
+ timeout: Annotated[float | None, Field(ge=0)] = None
115
+ max_retries: Annotated[int | None, Field(ge=0)] = None
116
+ default_headers: dict[str, str] | None = None
117
+ default_query: dict[str, str] | None = None
118
+ http_client: httpx.AsyncClient | None = None
119
+
120
+
110
121
  class BrowserbaseConnectOptions(WireModel):
111
122
  model_config = ConfigDict(extra="forbid", strict=True)
112
123
 
113
124
  api_key: Annotated[str, Field(min_length=1)]
114
125
  base_url: Annotated[str, Field(min_length=1)] = DEFAULT_BROWSERBASE_URL
126
+ client_options: BrowserbaseClientOptions | None = None
115
127
  session_id: Annotated[str, Field(min_length=1)]
116
128
  extension_id: Annotated[str | None, Field(min_length=1)] = None
117
129
 
@@ -3,6 +3,8 @@
3
3
  from collections.abc import Awaitable, Callable
4
4
  from typing import Literal, NotRequired, TypedDict
5
5
 
6
+ import httpx
7
+
6
8
  from ._generated.input_types import (
7
9
  ModelConfig,
8
10
  TelemetryConfig,
@@ -66,9 +68,21 @@ class LocalBrowserConnectOptions(TypedDict):
66
68
  extension_id: NotRequired[str]
67
69
 
68
70
 
71
+ class BrowserbaseClientOptions(TypedDict, total=False):
72
+ """HTTP settings for the Browserbase client Stagehand creates for session management."""
73
+
74
+ timeout: float
75
+ max_retries: int
76
+ default_headers: dict[str, str]
77
+ default_query: dict[str, str]
78
+ http_client: httpx.AsyncClient
79
+ """Caller-owned; Stagehand never closes it. Keep it open until `browser.close()` returns."""
80
+
81
+
69
82
  class BrowserbaseConnectOptions(TypedDict):
70
83
  api_key: str
71
84
  base_url: NotRequired[str]
85
+ client_options: NotRequired[BrowserbaseClientOptions]
72
86
  session_id: str
73
87
  extension_id: NotRequired[str]
74
88
 
@@ -96,6 +110,7 @@ class StagehandClientCreateConfig(TypedDict, total=False):
96
110
 
97
111
 
98
112
  __all__ = [
113
+ "BrowserbaseClientOptions",
99
114
  "BrowserbaseConnectOptions",
100
115
  "Cache",
101
116
  "CacheOptions",