stagehand 4.0.3a0.dev1475__tar.gz → 4.0.3a0.dev1476__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 (35) hide show
  1. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/PKG-INFO +1 -1
  2. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/pyproject.toml +1 -1
  3. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_extension/service-worker.js +7 -21
  4. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_generated/input_types.py +0 -4
  5. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_generated/models.py +0 -8
  6. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_generated/protocol_version.py +1 -1
  7. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/browser.py +56 -13
  8. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/browser_context.py +11 -9
  9. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/browserbase_session.py +18 -1
  10. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/rpc_client.py +0 -1
  11. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/stagehand.py +7 -3
  12. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/README.md +0 -0
  13. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/__init__.py +0 -0
  14. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_extension/blank.html +0 -0
  15. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_extension/content-script.js +0 -0
  16. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_extension/manifest.json +0 -0
  17. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_extension/offscreen/service-worker-heartbeat.html +0 -0
  18. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_extension/offscreen/service-worker-heartbeat.js +0 -0
  19. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_extension/wake-service-worker.html +0 -0
  20. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_extension/wake-service-worker.js +0 -0
  21. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_generated/__init__.py +0 -0
  22. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_sdk_identity.py +0 -0
  23. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/_validation.py +0 -0
  24. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/browser_clipboard.py +0 -0
  25. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/cdp_client.py +0 -0
  26. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/client_models.py +0 -0
  27. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/client_types.py +0 -0
  28. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/extension_assets.py +0 -0
  29. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/file_upload.py +0 -0
  30. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/locator.py +0 -0
  31. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/page.py +0 -0
  32. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/py.typed +0 -0
  33. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/response.py +0 -0
  34. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/timeouts.py +0 -0
  35. {stagehand-4.0.3a0.dev1475 → stagehand-4.0.3a0.dev1476}/src/stagehand/webmcp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stagehand
3
- Version: 4.0.3a0.dev1475
3
+ Version: 4.0.3a0.dev1476
4
4
  Summary: Browserbase's SDK for building browser agents
5
5
  Keywords: ai,browser,automation,web-scraping,testing
6
6
  Author: Browserbase
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stagehand"
3
- version = "4.0.3a0.dev1475"
3
+ version = "4.0.3a0.dev1476"
4
4
  description = "Browserbase's SDK for building browser agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -7359,7 +7359,7 @@ function isRecord$1(value) {
7359
7359
  }
7360
7360
  var package_default$1 = {
7361
7361
  name: "@browserbasehq/stagehand-protocol",
7362
- version: "2.0.0-alpha-341433acac46a305ad6c2f9a0445e907675f4fb4",
7362
+ version: "2.0.0-alpha-6555e81e837012088eb12a39c06aed0527a2e5af",
7363
7363
  "private": true,
7364
7364
  type: "module",
7365
7365
  scripts: {
@@ -8302,7 +8302,6 @@ var PageNavigationOptionsSchema = strictObject({
8302
8302
  }).meta({ id: "PageNavigationOptions" });
8303
8303
  var PageVoidResultSchema = strictObject({ ok: literal(true) }).meta({ id: "PageVoidResult" });
8304
8304
  var ContextVoidResultSchema = strictObject({ ok: literal(true) }).meta({ id: "ContextVoidResult" });
8305
- var ContextCloseResultSchema = strictObject({ closed: literal(true) }).meta({ id: "ContextCloseResult" });
8306
8305
  var PageScreenshotClipSchema = strictObject({
8307
8306
  x: number$1(),
8308
8307
  y: number$1(),
@@ -8784,11 +8783,6 @@ var StagehandMethods = {
8784
8783
  params: ContextSetActivePageParamsSchema,
8785
8784
  result: ContextVoidResultSchema
8786
8785
  },
8787
- contextClose: {
8788
- name: "context.close",
8789
- params: EmptyParamsSchema,
8790
- result: ContextCloseResultSchema
8791
- },
8792
8786
  contextAddInitScript: {
8793
8787
  name: "context.add_init_script",
8794
8788
  params: ContextAddInitScriptParamsSchema,
@@ -11555,10 +11549,6 @@ function createContextController(runtime) {
11555
11549
  logger.debug("context.set_active_page", {});
11556
11550
  return runtime.contextSetActivePage(params);
11557
11551
  }
11558
- async function close(_params, { logger }) {
11559
- logger.debug("context.close", {});
11560
- return runtime.contextClose();
11561
- }
11562
11552
  async function addInitScript(params, { logger }) {
11563
11553
  logger.debug("context.add_init_script", {});
11564
11554
  return runtime.contextAddInitScript(params);
@@ -11616,7 +11606,6 @@ function createContextController(runtime) {
11616
11606
  newPage,
11617
11607
  activePage,
11618
11608
  setActivePage,
11619
- close,
11620
11609
  addInitScript,
11621
11610
  setExtraHTTPHeaders,
11622
11611
  getDomainPolicy,
@@ -12335,8 +12324,9 @@ function reportPageEventListenerError(error) {
12335
12324
  //#endregion
12336
12325
  //#region ../sdk-ts/src/browserContext.ts
12337
12326
  var BrowserContext$1 = class {
12338
- constructor(rpcClient) {
12327
+ constructor(rpcClient, closeBrowser) {
12339
12328
  this.rpcClient = rpcClient;
12329
+ this.closeBrowser = closeBrowser;
12340
12330
  }
12341
12331
  get clipboard() {
12342
12332
  return this.clipboardRef ??= new BrowserClipboard(this.rpcClient);
@@ -12356,9 +12346,10 @@ var BrowserContext$1 = class {
12356
12346
  async setActivePage(page) {
12357
12347
  await this.rpcClient.send(StagehandMethods.contextSetActivePage, { pageId: page.pageId });
12358
12348
  }
12359
- /** Close the remote context. Call Stagehand.close() to dispose the SDK's local resources. */
12349
+ /** Close the underlying browser. */
12360
12350
  async close() {
12361
- await this.rpcClient.send(StagehandMethods.contextClose, {});
12351
+ if (!this.closeBrowser) throw new Error("Browser context is not attached to a browser");
12352
+ await this.closeBrowser();
12362
12353
  }
12363
12354
  async addInitScript(script, arg) {
12364
12355
  const source = await normalizeInitScriptSource$1(script, arg, "context.addInitScript");
@@ -51020,7 +51011,6 @@ var RPCRouter = class {
51020
51011
  case "context.new_page": return this.contextController.newPage(parseParams(StagehandMethods.contextNewPage, request.params), context);
51021
51012
  case "context.active_page": return this.contextController.activePage(parseParams(StagehandMethods.contextActivePage, request.params), context);
51022
51013
  case "context.set_active_page": return this.contextController.setActivePage(parseParams(StagehandMethods.contextSetActivePage, request.params), context);
51023
- case "context.close": return this.contextController.close(parseParams(StagehandMethods.contextClose, request.params), context);
51024
51014
  case "context.add_init_script": return this.contextController.addInitScript(parseParams(StagehandMethods.contextAddInitScript, request.params), context);
51025
51015
  case "context.set_extra_http_headers": return this.contextController.setExtraHTTPHeaders(parseParams(StagehandMethods.contextSetExtraHTTPHeaders, request.params), context);
51026
51016
  case "context.get_domain_policy": return this.contextController.getDomainPolicy(parseParams(StagehandMethods.contextGetDomainPolicy, request.params), context);
@@ -53447,7 +53437,7 @@ var WebTracerProvider = class extends BasicTracerProvider {
53447
53437
  };
53448
53438
  var package_default = {
53449
53439
  name: "@browserbasehq/stagehand-extension",
53450
- version: "1.0.2-alpha-341433acac46a305ad6c2f9a0445e907675f4fb4",
53440
+ version: "1.0.2-alpha-6555e81e837012088eb12a39c06aed0527a2e5af",
53451
53441
  "private": true,
53452
53442
  type: "module",
53453
53443
  scripts: {
@@ -57030,10 +57020,6 @@ var StagehandRuntime = class {
57030
57020
  await this.requireBrowserSession().setActivePage(page);
57031
57021
  return { ok: true };
57032
57022
  }
57033
- async contextClose() {
57034
- await this.close();
57035
- return { closed: true };
57036
- }
57037
57023
  async contextAddInitScript(params) {
57038
57024
  await this.requireBrowserSession().addInitScript(params.source);
57039
57025
  return { ok: true };
@@ -140,10 +140,6 @@ class ContextClipboardWriteTextParams(TypedDict):
140
140
  text: str
141
141
 
142
142
 
143
- class ContextCloseResult(TypedDict):
144
- closed: Literal[True]
145
-
146
-
147
143
  class ContextCookiesParams(TypedDict):
148
144
  urls: NotRequired[str | list[str]]
149
145
 
@@ -430,14 +430,6 @@ class ContextClipboardWriteTextParams(WireModel):
430
430
  text: StrictStr
431
431
 
432
432
 
433
- class ContextCloseResult(WireModel):
434
- model_config = ConfigDict(
435
- extra="forbid",
436
- validate_by_name=True,
437
- )
438
- closed: Literal[True]
439
-
440
-
441
433
  class ContextCookiesParams(WireModel):
442
434
  model_config = ConfigDict(
443
435
  extra="forbid",
@@ -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.0.0-alpha-341433acac46a305ad6c2f9a0445e907675f4fb4'
4
+ STAGEHAND_PROTOCOL_VERSION = '2.0.0-alpha-6555e81e837012088eb12a39c06aed0527a2e5af'
@@ -22,7 +22,7 @@ from ._generated.models import (
22
22
  BrowserSessionMetadata,
23
23
  )
24
24
  from .browserbase_session import DEFAULT_BROWSERBASE_URL, _create_browserbase_session_client
25
- from .cdp_client import CDPClient
25
+ from .cdp_client import CDPClient, CDPConnectionClosedError
26
26
  from .client_models import (
27
27
  BrowserbaseConnectOptions,
28
28
  LocalBrowserConnectOptions,
@@ -98,7 +98,8 @@ class StagehandBrowser:
98
98
  "_attachment",
99
99
  "_claimed",
100
100
  "_close_callback",
101
- "_close_task",
101
+ "_invalidate_callback",
102
+ "_terminal_task",
102
103
  "_context",
103
104
  "_origin",
104
105
  "_provider",
@@ -112,6 +113,7 @@ class StagehandBrowser:
112
113
  attachment: _ClaimedBrowser,
113
114
  close: Callable[[], Awaitable[None]],
114
115
  *,
116
+ invalidate: Callable[[], Awaitable[None]] | None = None,
115
117
  session_id: str | None = None,
116
118
  _token: object | None = None,
117
119
  ) -> None:
@@ -121,8 +123,9 @@ class StagehandBrowser:
121
123
  self._origin = origin
122
124
  self._attachment = attachment
123
125
  self._close_callback = close
126
+ self._invalidate_callback = invalidate or close
124
127
  self._claimed = False
125
- self._close_task: asyncio.Task[None] | None = None
128
+ self._terminal_task: asyncio.Task[None] | None = None
126
129
  self._context: BrowserContext | None = None
127
130
  self._session_id = session_id
128
131
 
@@ -141,7 +144,7 @@ class StagehandBrowser:
141
144
 
142
145
  @property
143
146
  def closed(self) -> bool:
144
- return self._close_task is not None
147
+ return self._terminal_task is not None
145
148
 
146
149
  @property
147
150
  def context(self) -> BrowserContext:
@@ -153,15 +156,12 @@ class StagehandBrowser:
153
156
  return self._context
154
157
 
155
158
  def close(self) -> Awaitable[None]:
156
- if self._close_task is None:
157
- self._close_task = asyncio.create_task(
158
- self._run_close(),
159
+ if self._terminal_task is None:
160
+ self._terminal_task = asyncio.create_task(
161
+ _run_browser_terminal_callback(self._close_callback),
159
162
  name="stagehand-browser-close",
160
163
  )
161
- return asyncio.shield(self._close_task)
162
-
163
- async def _run_close(self) -> None:
164
- await self._close_callback()
164
+ return asyncio.shield(self._terminal_task)
165
165
 
166
166
 
167
167
  def _claim_browser(browser: object) -> _ClaimedBrowser:
@@ -181,6 +181,23 @@ def _release_browser(browser: StagehandBrowser) -> None:
181
181
  browser._claimed = False
182
182
 
183
183
 
184
+ def _invalidate_browser(browser: StagehandBrowser) -> Awaitable[None]:
185
+ if not isinstance(browser, StagehandBrowser):
186
+ raise TypeError("browser must be created by local_browser or browserbase")
187
+ if browser._terminal_task is None:
188
+ browser._terminal_task = asyncio.create_task(
189
+ _run_browser_terminal_callback(browser._invalidate_callback),
190
+ name="stagehand-browser-invalidate",
191
+ )
192
+ return asyncio.shield(browser._terminal_task)
193
+
194
+
195
+ async def _run_browser_terminal_callback(
196
+ callback: Callable[[], Awaitable[None]],
197
+ ) -> None:
198
+ await callback()
199
+
200
+
184
201
  def _attach_browser_context(
185
202
  browser: StagehandBrowser,
186
203
  context: BrowserContext,
@@ -289,13 +306,34 @@ async def _connect_browser(
289
306
 
290
307
  connected_client = cdp_client
291
308
 
292
- async def close() -> None:
309
+ async def invalidate() -> None:
293
310
  try:
294
311
  await connected_client.close()
295
312
  finally:
296
313
  if owns_source:
297
314
  await source.close()
298
315
 
316
+ async def close() -> None:
317
+ errors: list[BaseException] = []
318
+ try:
319
+ if provider == "local" and origin == "connected":
320
+ try:
321
+ await connected_client.send_command("Browser.close")
322
+ except CDPConnectionClosedError:
323
+ pass
324
+ else:
325
+ await source.close()
326
+ except BaseException as error:
327
+ errors.append(error)
328
+ try:
329
+ await connected_client.close()
330
+ except BaseException as error:
331
+ errors.append(error)
332
+ if len(errors) == 1:
333
+ raise errors[0]
334
+ if errors:
335
+ raise BaseExceptionGroup("Browser termination and cleanup failed", errors)
336
+
299
337
  return StagehandBrowser(
300
338
  provider,
301
339
  origin,
@@ -304,6 +342,7 @@ async def _connect_browser(
304
342
  worker_init_metadata=worker_init_metadata,
305
343
  ),
306
344
  close,
345
+ invalidate=invalidate,
307
346
  session_id=(
308
347
  worker_init_metadata.browser.session_id if worker_init_metadata.browser else None
309
348
  ),
@@ -563,7 +602,11 @@ class BrowserbaseBrowser:
563
602
  return await _connect_browser(
564
603
  provider="browserbase",
565
604
  origin="connected",
566
- source=_ConnectedBrowserSource(connection.cdp_url),
605
+ source=ResolvedBrowserSource(
606
+ cdp_url=connection.cdp_url,
607
+ keep_alive=True,
608
+ _close_callback=connection.close,
609
+ ),
567
610
  extension_id=options.extension_id,
568
611
  preloaded_extension=options.extension_id is None,
569
612
  worker_init_metadata=_WorkerInitMetadata(
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- from collections.abc import Mapping, Sequence
4
+ from collections.abc import Awaitable, Callable, Mapping, Sequence
5
5
  from pathlib import Path
6
6
 
7
7
  from ._generated.input_types import CookieParam
@@ -12,7 +12,6 @@ from ._generated.models import (
12
12
  ContextAddCookiesParams,
13
13
  ContextAddInitScriptParams,
14
14
  ContextClearCookiesParams,
15
- ContextCloseResult,
16
15
  ContextCookiesParams,
17
16
  ContextCookiesResult,
18
17
  ContextGetDomainPolicyResult,
@@ -35,8 +34,13 @@ from .rpc_client import RPCClient
35
34
 
36
35
 
37
36
  class BrowserContext:
38
- def __init__(self, rpc_client: RPCClient) -> None:
37
+ def __init__(
38
+ self,
39
+ rpc_client: RPCClient,
40
+ close_browser: Callable[[], Awaitable[None]] | None = None,
41
+ ) -> None:
39
42
  self._rpc_client = rpc_client
43
+ self._close_browser = close_browser
40
44
  self._clipboard: BrowserClipboard | None = None
41
45
 
42
46
  @property
@@ -76,12 +80,10 @@ class BrowserContext:
76
80
  )
77
81
 
78
82
  async def close(self) -> None:
79
- """Close the remote context; use Stagehand.close() to release local resources."""
80
- await self._rpc_client.send(
81
- "context.close",
82
- EmptyParams(),
83
- ContextCloseResult,
84
- )
83
+ """Close the underlying browser."""
84
+ if self._close_browser is None:
85
+ raise RuntimeError("Browser context is not attached to a browser")
86
+ await self._close_browser()
85
87
 
86
88
  async def add_init_script(self, source: str | Path) -> None:
87
89
  if isinstance(source, Path):
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- from collections.abc import Mapping
4
+ from collections.abc import Awaitable, Callable, Mapping
5
5
  from dataclasses import dataclass, field
6
6
  from typing import Any, Protocol
7
7
 
@@ -142,6 +142,11 @@ class _BrowserbaseSessionConnection:
142
142
  session_id: str
143
143
  cdp_url: str
144
144
  region: BrowserbaseRegion | None = None
145
+ _close_callback: Callable[[], Awaitable[None]] | None = field(default=None, repr=False)
146
+
147
+ async def close(self) -> None:
148
+ if self._close_callback is not None:
149
+ await self._close_callback()
145
150
 
146
151
 
147
152
  @dataclass
@@ -254,10 +259,22 @@ class _BrowserbaseSessionClient:
254
259
  cdp_url = connect_url.strip() if connect_url is not None else ""
255
260
  if not cdp_url:
256
261
  raise BrowserbaseSessionError("Browserbase session is not available for connection")
262
+ released = False
263
+ close_lock = asyncio.Lock()
264
+
265
+ async def close() -> None:
266
+ nonlocal released
267
+ async with close_lock:
268
+ if released:
269
+ return
270
+ await self._api.release_session(retrieved_id.strip() or normalized_session_id)
271
+ released = True
272
+
257
273
  return _BrowserbaseSessionConnection(
258
274
  session_id=retrieved_id.strip() or normalized_session_id,
259
275
  cdp_url=cdp_url,
260
276
  region=region,
277
+ _close_callback=close,
261
278
  )
262
279
 
263
280
  async def _delete_extension_best_effort(self, extension_id: str | None) -> None:
@@ -38,7 +38,6 @@ _UNBOUNDED_BY_DEFAULT_METHODS = {
38
38
  "stagehand.extract",
39
39
  "stagehand.observe",
40
40
  "context.new_page",
41
- "context.close",
42
41
  "context.add_init_script",
43
42
  "context.set_extra_http_headers",
44
43
  "context.get_domain_policy",
@@ -47,6 +47,7 @@ from .browser import (
47
47
  _claim_browser,
48
48
  _ClaimedBrowser,
49
49
  _detach_browser_context,
50
+ _invalidate_browser,
50
51
  _release_browser,
51
52
  )
52
53
  from .browser_context import BrowserContext
@@ -278,7 +279,10 @@ class Stagehand:
278
279
  init_params,
279
280
  StagehandInitResult,
280
281
  )
281
- _attach_browser_context(self._browser_handle, BrowserContext(rpc_client))
282
+ _attach_browser_context(
283
+ self._browser_handle,
284
+ BrowserContext(rpc_client, self._browser_handle.close),
285
+ )
282
286
  self._initialized = True
283
287
 
284
288
  async def act(
@@ -547,11 +551,11 @@ class Stagehand:
547
551
  fail_closed: bool,
548
552
  ) -> None:
549
553
  if fail_closed:
550
- browser_close = browser.close()
554
+ browser_invalidation = _invalidate_browser(browser)
551
555
  try:
552
556
  await self._release_resources()
553
557
  finally:
554
- await browser_close
558
+ await browser_invalidation
555
559
  return
556
560
 
557
561
  try: