Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release-doctor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Release Doctor
on:
pull_request:
branches:
- stainless
- main
workflow_dispatch:

jobs:
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "3.4.8"
".": "3.5.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 8
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-089c8670f1d7c2e9fa8e5c97010db7c24b8f162eb7cfe76ffa41d70fa46efe2f.yml
openapi_spec_hash: 7a226aee8f3f2ab16febbe6bb35e1657
config_hash: 242651c4871c2869ba3c2e3d337505b9
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-43e6dd4ce19381de488d296e9036fea15bfea9a6f946cf8ccf4e02aecc8fb765.yml
openapi_spec_hash: f736e7a8acea0d73e1031c86ea803246
config_hash: b375728ccf7d33287335852f4f59c293
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 3.5.0 (2026-01-29)

Full Changelog: [v3.4.8...v3.5.0](https://github.com/browserbase/stagehand-python/compare/v3.4.8...v3.5.0)

### Features

* add auto-bedrock support based on bedrock/provider.model-name ([eaded9f](https://github.com/browserbase/stagehand-python/commit/eaded9ffb050c297b86223c333044d8c22dd3cf4))
* Update stainless.yml for project and publish settings ([f90c553](https://github.com/browserbase/stagehand-python/commit/f90c55378c03c18215d1cdc153f84d587e5048b0))


### Bug Fixes

* **docs:** fix mcp installation instructions for remote servers ([85f8584](https://github.com/browserbase/stagehand-python/commit/85f85840c9e9de4c0c1b07ec1ef41936788ea88b))


### Chores

* **internal:** version bump ([d227b02](https://github.com/browserbase/stagehand-python/commit/d227b0213aa729243fbc56d818a808536b98b191))
* update SDK settings ([879b799](https://github.com/browserbase/stagehand-python/commit/879b7990e8095ca106bf9553159d6c7a01936ec9))

## 3.4.8 (2026-01-27)

Full Changelog: [v3.4.7...v3.4.8](https://github.com/browserbase/stagehand-python/compare/v3.4.7...v3.4.8)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,9 @@ session = response.parse() # get the object that `sessions.start()` would have
print(session.data)
```

These methods return an [`APIResponse`](https://github.com/browserbase/stagehand-python/tree/stainless/src/stagehand/_response.py) object.
These methods return an [`APIResponse`](https://github.com/browserbase/stagehand-python/tree/main/src/stagehand/_response.py) object.

The async client returns an [`AsyncAPIResponse`](https://github.com/browserbase/stagehand-python/tree/stainless/src/stagehand/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.
The async client returns an [`AsyncAPIResponse`](https://github.com/browserbase/stagehand-python/tree/main/src/stagehand/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.

#### `.with_streaming_response`

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "stagehand"
version = "3.4.8"
version = "3.5.0"
description = "The official Python library for the stagehand API"
dynamic = ["readme"]
license = "MIT"
Expand Down Expand Up @@ -122,7 +122,7 @@ path = "README.md"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
# replace relative links with absolute links
pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
replacement = '[\1](https://github.com/browserbase/stagehand-python/tree/stainless/\g<2>)'
replacement = '[\1](https://github.com/browserbase/stagehand-python/tree/main/\g<2>)'

[tool.pytest.ini_options]
testpaths = ["tests"]
Expand Down
7 changes: 4 additions & 3 deletions src/stagehand/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

import os
import datetime
from typing import TYPE_CHECKING, Any, Mapping
from typing_extensions import Self, Literal, override

Expand Down Expand Up @@ -220,7 +219,6 @@ def default_headers(self) -> dict[str, str | Omit]:
**super().default_headers,
"x-language": "python",
"x-sdk-version": __version__,
"x-sent-at": datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z"),
"X-Stainless-Async": "false",
**self._custom_headers,
}
Expand All @@ -236,6 +234,7 @@ def copy(
local_host: str | None = None,
local_port: int | None = None,
local_headless: bool | None = None,
local_chrome_path: str | None = None,
local_ready_timeout_s: float | None = None,
local_openai_api_key: str | None = None,
local_shutdown_on_close: bool | None = None,
Expand Down Expand Up @@ -280,6 +279,7 @@ def copy(
local_host=local_host or self._local_host,
local_port=local_port if local_port is not None else self._local_port,
local_headless=local_headless if local_headless is not None else self._local_headless,
local_chrome_path=local_chrome_path if local_chrome_path is not None else self._local_chrome_path,
local_ready_timeout_s=local_ready_timeout_s
if local_ready_timeout_s is not None
else self._local_ready_timeout_s,
Expand Down Expand Up @@ -506,7 +506,6 @@ def default_headers(self) -> dict[str, str | Omit]:
**super().default_headers,
"x-language": "python",
"x-sdk-version": __version__,
"x-sent-at": datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z"),
"X-Stainless-Async": f"async:{get_async_library()}",
**self._custom_headers,
}
Expand All @@ -522,6 +521,7 @@ def copy(
local_host: str | None = None,
local_port: int | None = None,
local_headless: bool | None = None,
local_chrome_path: str | None = None,
local_ready_timeout_s: float | None = None,
local_openai_api_key: str | None = None,
local_shutdown_on_close: bool | None = None,
Expand Down Expand Up @@ -566,6 +566,7 @@ def copy(
local_host=local_host or self._local_host,
local_port=local_port if local_port is not None else self._local_port,
local_headless=local_headless if local_headless is not None else self._local_headless,
local_chrome_path=local_chrome_path if local_chrome_path is not None else self._local_chrome_path,
local_ready_timeout_s=local_ready_timeout_s
if local_ready_timeout_s is not None
else self._local_ready_timeout_s,
Expand Down
11 changes: 7 additions & 4 deletions src/stagehand/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,12 +857,15 @@ def construct( # type: ignore
_fields_set: set[str] | None = None,
**values: Unpack[FinalRequestOptionsInput],
) -> FinalRequestOptions:
kwargs: dict[str, Any] = {
kwargs: dict[str, Any] = {}
for key, value in values.items():
if key == "headers" and is_mapping(value):
# Preserve Omit() for headers so callers can explicitly remove defaults.
kwargs[key] = {k: v for k, v in value.items() if not isinstance(v, NotGiven)}
continue
# we unconditionally call `strip_not_given` on any value
# as it will just ignore any non-mapping types
key: strip_not_given(value)
for key, value in values.items()
}
kwargs[key] = strip_not_given(value)
if PYDANTIC_V1:
return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated]
return super().model_construct(_fields_set, **kwargs)
Expand Down
4 changes: 2 additions & 2 deletions src/stagehand/_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def __stream__(self) -> Iterator[_T]:

try:
for sse in iterator:
if sse.data.startswith("finished"):
if sse.data.startswith('{"data":{"status":"finished"'):
break

if sse.data.startswith("error"):
Expand Down Expand Up @@ -139,7 +139,7 @@ async def __stream__(self) -> AsyncIterator[_T]:

try:
async for sse in iterator:
if sse.data.startswith("finished"):
if sse.data.startswith('{"data":{"status":"finished"'):
break

if sse.data.startswith("error"):
Expand Down
2 changes: 1 addition & 1 deletion src/stagehand/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "stagehand"
__version__ = "3.4.8" # x-release-please-version
__version__ = "3.5.0" # x-release-please-version
6 changes: 0 additions & 6 deletions src/stagehand/resources/sessions_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from __future__ import annotations

from typing import Union
from datetime import datetime
from typing_extensions import Literal, override

import httpx
Expand Down Expand Up @@ -79,7 +77,6 @@ def start(
system_prompt: str | Omit = omit,
verbose: Literal[0, 1, 2] | Omit = omit,
wait_for_captcha_solves: bool | Omit = omit,
x_sent_at: Union[str, datetime] | Omit = omit,
x_stream_response: Literal["true", "false"] | Omit = omit,
extra_headers: Headers | None = None,
extra_query: Query | None = None,
Expand All @@ -98,7 +95,6 @@ def start(
system_prompt=system_prompt,
verbose=verbose,
wait_for_captcha_solves=wait_for_captcha_solves,
x_sent_at=x_sent_at,
x_stream_response=x_stream_response,
extra_headers=extra_headers,
extra_query=extra_query,
Expand Down Expand Up @@ -134,7 +130,6 @@ async def start(
system_prompt: str | Omit = omit,
verbose: Literal[0, 1, 2] | Omit = omit,
wait_for_captcha_solves: bool | Omit = omit,
x_sent_at: Union[str, datetime] | Omit = omit,
x_stream_response: Literal["true", "false"] | Omit = omit,
extra_headers: Headers | None = None,
extra_query: Query | None = None,
Expand All @@ -153,7 +148,6 @@ async def start(
system_prompt=system_prompt,
verbose=verbose,
wait_for_captcha_solves=wait_for_captcha_solves,
x_sent_at=x_sent_at,
x_stream_response=x_stream_response,
extra_headers=extra_headers,
extra_query=extra_query,
Expand Down
7 changes: 1 addition & 6 deletions src/stagehand/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from __future__ import annotations

import inspect
from typing import TYPE_CHECKING, Any, Union, cast
from datetime import datetime
from typing import TYPE_CHECKING, Any, cast
from typing_extensions import Unpack, Literal, Protocol

import httpx
Expand Down Expand Up @@ -248,7 +247,6 @@ def execute(
def end(
self,
*,
x_sent_at: Union[str, datetime] | Omit = omit,
x_stream_response: Literal["true", "false"] | Omit = omit,
extra_headers: Headers | None = None,
extra_query: Query | None = None,
Expand All @@ -257,7 +255,6 @@ def end(
) -> SessionEndResponse:
return self._client.sessions.end(
id=self.id,
x_sent_at=x_sent_at,
x_stream_response=x_stream_response,
extra_headers=extra_headers,
extra_query=extra_query,
Expand Down Expand Up @@ -385,7 +382,6 @@ async def execute(
async def end(
self,
*,
x_sent_at: Union[str, datetime] | Omit = omit,
x_stream_response: Literal["true", "false"] | Omit = omit,
extra_headers: Headers | None = None,
extra_query: Query | None = None,
Expand All @@ -394,7 +390,6 @@ async def end(
) -> SessionEndResponse:
return await self._client.sessions.end(
id=self.id,
x_sent_at=x_sent_at,
x_stream_response=x_stream_response,
extra_headers=extra_headers,
extra_query=extra_query,
Expand Down
8 changes: 7 additions & 1 deletion src/stagehand/types/session_execute_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ class SessionExecuteParamsBase(TypedDict, total=False):

class AgentConfig(TypedDict, total=False):
cua: bool
"""Enable Computer Use Agent mode"""
"""Deprecated.

Use mode: 'cua' instead. If both are provided, mode takes precedence.
"""

mode: Literal["dom", "hybrid", "cua"]
"""Tool mode for the agent (dom, hybrid, cua). If set, overrides cua."""

model: ModelConfigParam
"""
Expand Down
Loading