From 0c9bb8cb3b791bf8c60ad0065fed9ad16b912b8e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 00:27:58 +0000 Subject: [PATCH 1/9] chore: sync repo --- .github/workflows/release-doctor.yml | 2 +- .stats.yml | 6 +- README.md | 6 +- pyproject.toml | 4 +- requirements-dev.lock | 12 +- src/stagehand/_streaming.py | 4 +- tests/api_resources/test_sessions.py | 24 ++++ uv.lock | 172 +++++++++++++-------------- 8 files changed, 127 insertions(+), 103 deletions(-) diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index aba0c08f..dba7811e 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -2,7 +2,7 @@ name: Release Doctor on: pull_request: branches: - - main + - stainless workflow_dispatch: jobs: diff --git a/.stats.yml b/.stats.yml index da476711..e42f0bb7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-43e6dd4ce19381de488d296e9036fea15bfea9a6f946cf8ccf4e02aecc8fb765.yml -openapi_spec_hash: f736e7a8acea0d73e1031c86ea803246 -config_hash: b375728ccf7d33287335852f4f59c293 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-8fbb3fa8f3a37c1c7408de427fe125aadec49f705e8e30d191601a9b69c4cc41.yml +openapi_spec_hash: 48b4dfac35a842d7fb0d228caf87544e +config_hash: 242651c4871c2869ba3c2e3d337505b9 diff --git a/README.md b/README.md index 07d0faba..a88efa73 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@
-[)](https://pypi.org/project/stagehand/) +[)](https://pypi.org/project/stagehand-alpha/)
@@ -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/main/src/stagehand/_response.py) object.
+These methods return an [`APIResponse`](https://github.com/browserbase/stagehand-python/tree/stainless/src/stagehand/_response.py) object.
-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.
+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.
#### `.with_streaming_response`
diff --git a/pyproject.toml b/pyproject.toml
index 3ee76886..4b7aeab0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
[project]
-name = "stagehand"
+name = "stagehand-alpha"
version = "3.5.0"
description = "The official Python library for the stagehand API"
dynamic = ["readme"]
@@ -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/main/\g<2>)'
+replacement = '[\1](https://github.com/browserbase/stagehand-python/tree/stainless/\g<2>)'
[tool.pytest.ini_options]
testpaths = ["tests"]
diff --git a/requirements-dev.lock b/requirements-dev.lock
index d6a2ab28..74fd3e19 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -6,7 +6,7 @@ annotated-types==0.7.0
anyio==4.12.0
# via
# httpx
- # stagehand
+ # stagehand-alpha
backports-asyncio-runner==1.2.0 ; python_full_version < '3.11'
# via pytest-asyncio
certifi==2025.11.12
@@ -17,7 +17,7 @@ colorama==0.4.6 ; sys_platform == 'win32'
# via pytest
dirty-equals==0.11
distro==1.9.0
- # via stagehand
+ # via stagehand-alpha
dotenv==0.9.9
exceptiongroup==1.3.1 ; python_full_version < '3.11'
# via
@@ -32,7 +32,7 @@ httpcore==1.0.9
httpx==0.28.1
# via
# respx
- # stagehand
+ # stagehand-alpha
idna==3.11
# via
# anyio
@@ -60,7 +60,7 @@ pathspec==0.12.1
pluggy==1.6.0
# via pytest
pydantic==2.12.5
- # via stagehand
+ # via stagehand-alpha
pydantic-core==2.41.5
# via pydantic
pygments==2.19.2
@@ -89,7 +89,7 @@ ruff==0.14.7
six==1.17.0 ; python_full_version < '3.10'
# via python-dateutil
sniffio==1.3.1
- # via stagehand
+ # via stagehand-alpha
time-machine==2.19.0 ; python_full_version < '3.10'
time-machine==3.1.0 ; python_full_version >= '3.10'
tomli==2.3.0 ; python_full_version < '3.11'
@@ -105,7 +105,7 @@ typing-extensions==4.15.0
# pydantic-core
# pyright
# pytest-asyncio
- # stagehand
+ # stagehand-alpha
# typing-inspection
typing-inspection==0.4.2
# via pydantic
diff --git a/src/stagehand/_streaming.py b/src/stagehand/_streaming.py
index 2a03e563..7790c9c0 100644
--- a/src/stagehand/_streaming.py
+++ b/src/stagehand/_streaming.py
@@ -56,7 +56,7 @@ def __stream__(self) -> Iterator[_T]:
try:
for sse in iterator:
- if sse.data.startswith('{"data":{"status":"finished"'):
+ if sse.data.startswith("finished"):
break
if sse.data.startswith("error"):
@@ -139,7 +139,7 @@ async def __stream__(self) -> AsyncIterator[_T]:
try:
async for sse in iterator:
- if sse.data.startswith('{"data":{"status":"finished"'):
+ if sse.data.startswith("finished"):
break
if sse.data.startswith("error"):
diff --git a/tests/api_resources/test_sessions.py b/tests/api_resources/test_sessions.py
index a4ca7867..5a843fd2 100644
--- a/tests/api_resources/test_sessions.py
+++ b/tests/api_resources/test_sessions.py
@@ -225,6 +225,12 @@ def test_method_execute_with_all_params_overload_1(self, client: Stagehand) -> N
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
agent_config={
"cua": True,
+ "execution_model": {
+ "model_name": "openai/gpt-5-nano",
+ "api_key": "sk-some-openai-api-key",
+ "base_url": "https://api.openai.com/v1",
+ "provider": "openai",
+ },
"mode": "cua",
"model": {"model_name": "openai/gpt-5-nano"},
"provider": "openai",
@@ -308,6 +314,12 @@ def test_method_execute_with_all_params_overload_2(self, client: Stagehand) -> N
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
agent_config={
"cua": True,
+ "execution_model": {
+ "model_name": "openai/gpt-5-nano",
+ "api_key": "sk-some-openai-api-key",
+ "base_url": "https://api.openai.com/v1",
+ "provider": "openai",
+ },
"mode": "cua",
"model": {"model_name": "openai/gpt-5-nano"},
"provider": "openai",
@@ -1058,6 +1070,12 @@ async def test_method_execute_with_all_params_overload_1(self, async_client: Asy
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
agent_config={
"cua": True,
+ "execution_model": {
+ "model_name": "openai/gpt-5-nano",
+ "api_key": "sk-some-openai-api-key",
+ "base_url": "https://api.openai.com/v1",
+ "provider": "openai",
+ },
"mode": "cua",
"model": {"model_name": "openai/gpt-5-nano"},
"provider": "openai",
@@ -1141,6 +1159,12 @@ async def test_method_execute_with_all_params_overload_2(self, async_client: Asy
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
agent_config={
"cua": True,
+ "execution_model": {
+ "model_name": "openai/gpt-5-nano",
+ "api_key": "sk-some-openai-api-key",
+ "base_url": "https://api.openai.com/v1",
+ "provider": "openai",
+ },
"mode": "cua",
"model": {"model_name": "openai/gpt-5-nano"},
"provider": "openai",
diff --git a/uv.lock b/uv.lock
index d7283983..daab5c16 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,17 +2,17 @@ version = 1
revision = 3
requires-python = ">=3.9"
resolution-markers = [
- "python_full_version >= '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version < '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra == 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
- "python_full_version < '3.10' and extra == 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
- "python_full_version < '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
+ "python_full_version >= '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version < '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version < '3.10' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version < '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
]
conflicts = [[
- { package = "stagehand", group = "pydantic-v1" },
- { package = "stagehand", group = "pydantic-v2" },
+ { package = "stagehand-alpha", group = "pydantic-v1" },
+ { package = "stagehand-alpha", group = "pydantic-v2" },
]]
[[package]]
@@ -31,7 +31,7 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
{ name = "aiosignal" },
- { name = "async-timeout", marker = "python_full_version < '3.11' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "async-timeout", marker = "python_full_version < '3.11' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
{ name = "attrs" },
{ name = "frozenlist" },
{ name = "multidict" },
@@ -167,7 +167,7 @@ version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "frozenlist" },
- { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" }
wheels = [
@@ -188,9 +188,9 @@ name = "anyio"
version = "4.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
{ name = "idna" },
- { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" }
wheels = [
@@ -276,7 +276,7 @@ name = "exceptiongroup"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
wheels = [
@@ -517,10 +517,10 @@ name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra == 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
+ "python_full_version >= '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
]
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
@@ -535,7 +535,7 @@ resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
- { name = "mdurl", marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "mdurl", marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
wheels = [
@@ -547,13 +547,13 @@ name = "markdown-it-py"
version = "4.0.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra == 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
+ "python_full_version >= '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
]
dependencies = [
- { name = "mdurl", marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "mdurl", marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
wheels = [
@@ -574,7 +574,7 @@ name = "multidict"
version = "6.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" }
wheels = [
@@ -732,7 +732,7 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "pathspec" },
- { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" }
@@ -953,7 +953,7 @@ resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
- { name = "typing-extensions", marker = "extra == 'group-9-stagehand-pydantic-v1'" },
+ { name = "typing-extensions", marker = "extra == 'group-15-stagehand-alpha-pydantic-v1'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/8d/7b346ed940c3e0f9eee7db9be37915a6dac0d9535d736e2ca47a81a066f3/pydantic-1.10.24.tar.gz", hash = "sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53", size = 357314, upload-time = "2025-09-25T01:36:33.065Z" }
wheels = [
@@ -1000,17 +1000,17 @@ name = "pydantic"
version = "2.12.5"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version < '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
- "python_full_version < '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
+ "python_full_version >= '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version < '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version < '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
]
dependencies = [
- { name = "annotated-types", marker = "extra == 'group-9-stagehand-pydantic-v2' or extra != 'group-9-stagehand-pydantic-v1'" },
- { name = "pydantic-core", marker = "extra == 'group-9-stagehand-pydantic-v2' or extra != 'group-9-stagehand-pydantic-v1'" },
- { name = "typing-extensions", marker = "extra == 'group-9-stagehand-pydantic-v2' or extra != 'group-9-stagehand-pydantic-v1'" },
- { name = "typing-inspection", marker = "extra == 'group-9-stagehand-pydantic-v2' or extra != 'group-9-stagehand-pydantic-v1'" },
+ { name = "annotated-types", marker = "extra == 'group-15-stagehand-alpha-pydantic-v2' or extra != 'group-15-stagehand-alpha-pydantic-v1'" },
+ { name = "pydantic-core", marker = "extra == 'group-15-stagehand-alpha-pydantic-v2' or extra != 'group-15-stagehand-alpha-pydantic-v1'" },
+ { name = "typing-extensions", marker = "extra == 'group-15-stagehand-alpha-pydantic-v2' or extra != 'group-15-stagehand-alpha-pydantic-v1'" },
+ { name = "typing-inspection", marker = "extra == 'group-15-stagehand-alpha-pydantic-v2' or extra != 'group-15-stagehand-alpha-pydantic-v1'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
wheels = [
@@ -1022,7 +1022,7 @@ name = "pydantic-core"
version = "2.41.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "typing-extensions", marker = "extra == 'group-9-stagehand-pydantic-v2' or extra != 'group-9-stagehand-pydantic-v1'" },
+ { name = "typing-extensions", marker = "extra == 'group-15-stagehand-alpha-pydantic-v2' or extra != 'group-15-stagehand-alpha-pydantic-v1'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
wheels = [
@@ -1178,13 +1178,13 @@ resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
- { name = "colorama", marker = "(python_full_version < '3.10' and sys_platform == 'win32') or (python_full_version >= '3.10' and extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2') or (sys_platform != 'win32' and extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "exceptiongroup", marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "packaging", marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "pluggy", marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "pygments", marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "tomli", marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "colorama", marker = "(python_full_version < '3.10' and sys_platform == 'win32') or (python_full_version >= '3.10' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2') or (sys_platform != 'win32' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "packaging", marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "pluggy", marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "pygments", marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "tomli", marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
wheels = [
@@ -1196,19 +1196,19 @@ name = "pytest"
version = "9.0.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra == 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
+ "python_full_version >= '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
]
dependencies = [
- { name = "colorama", marker = "(python_full_version >= '3.10' and sys_platform == 'win32') or (python_full_version < '3.10' and extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2') or (sys_platform != 'win32' and extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "exceptiongroup", marker = "python_full_version == '3.10.*' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "packaging", marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "pluggy", marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "pygments", marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "tomli", marker = "python_full_version == '3.10.*' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "colorama", marker = "(python_full_version >= '3.10' and sys_platform == 'win32') or (python_full_version < '3.10' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2') or (sys_platform != 'win32' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "exceptiongroup", marker = "python_full_version == '3.10.*' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "packaging", marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "pluggy", marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "pygments", marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "tomli", marker = "python_full_version == '3.10.*' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" }
wheels = [
@@ -1223,9 +1223,9 @@ resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
- { name = "backports-asyncio-runner", marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "typing-extensions", marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "backports-asyncio-runner", marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" }
wheels = [
@@ -1237,15 +1237,15 @@ name = "pytest-asyncio"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra == 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
+ "python_full_version >= '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
]
dependencies = [
- { name = "backports-asyncio-runner", marker = "python_full_version == '3.10.*' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "typing-extensions", marker = "(python_full_version >= '3.10' and python_full_version < '3.13') or (python_full_version < '3.10' and extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2') or (python_full_version >= '3.13' and extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "backports-asyncio-runner", marker = "python_full_version == '3.10.*' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "typing-extensions", marker = "(python_full_version >= '3.10' and python_full_version < '3.13') or (python_full_version < '3.10' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2') or (python_full_version >= '3.13' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" }
wheels = [
@@ -1258,8 +1258,8 @@ version = "3.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "execnet" },
- { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" }
wheels = [
@@ -1271,7 +1271,7 @@ name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "six", marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "six", marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
wheels = [
@@ -1304,8 +1304,8 @@ name = "rich"
version = "14.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
@@ -1358,15 +1358,15 @@ wheels = [
]
[[package]]
-name = "stagehand"
-version = "3.4.8"
+name = "stagehand-alpha"
+version = "3.5.0"
source = { editable = "." }
dependencies = [
{ name = "anyio" },
{ name = "distro" },
{ name = "httpx" },
- { name = "pydantic", version = "1.10.24", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-9-stagehand-pydantic-v1'" },
- { name = "pydantic", version = "2.12.5", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-9-stagehand-pydantic-v2' or extra != 'group-9-stagehand-pydantic-v1'" },
+ { name = "pydantic", version = "1.10.24", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-15-stagehand-alpha-pydantic-v1'" },
+ { name = "pydantic", version = "2.12.5", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-15-stagehand-alpha-pydantic-v2' or extra != 'group-15-stagehand-alpha-pydantic-v1'" },
{ name = "sniffio" },
{ name = "typing-extensions" },
]
@@ -1384,16 +1384,16 @@ dev = [
{ name = "importlib-metadata" },
{ name = "mypy" },
{ name = "pyright" },
- { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "pytest-asyncio", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "pytest-asyncio", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "pytest-asyncio", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "pytest-asyncio", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
{ name = "pytest-xdist" },
{ name = "respx" },
{ name = "rich" },
{ name = "ruff" },
- { name = "time-machine", version = "2.19.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
- { name = "time-machine", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "time-machine", version = "2.19.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
+ { name = "time-machine", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
pydantic-v1 = [
{ name = "pydantic", version = "1.10.24", source = { registry = "https://pypi.org/simple" } },
@@ -1444,7 +1444,7 @@ resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
- { name = "python-dateutil", marker = "python_full_version < '3.10' or (extra == 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2')" },
+ { name = "python-dateutil", marker = "python_full_version < '3.10' or (extra == 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/1b5fdd165f61b67f445fac2a7feb0c655118edef429cd09ff5a8067f7f1d/time_machine-2.19.0.tar.gz", hash = "sha256:7c5065a8b3f2bbb449422c66ef71d114d3f909c276a6469642ecfffb6a0fcd29", size = 14576, upload-time = "2025-08-19T17:22:08.402Z" }
wheels = [
@@ -1543,10 +1543,10 @@ name = "time-machine"
version = "3.1.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version >= '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-9-stagehand-pydantic-v1' and extra == 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra == 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
- "python_full_version >= '3.10' and extra != 'group-9-stagehand-pydantic-v1' and extra != 'group-9-stagehand-pydantic-v2'",
+ "python_full_version >= '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra == 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra == 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
+ "python_full_version >= '3.10' and extra != 'group-15-stagehand-alpha-pydantic-v1' and extra != 'group-15-stagehand-alpha-pydantic-v2'",
]
sdist = { url = "https://files.pythonhosted.org/packages/17/bd/a1bb03eb39ce35c966f0dde6559df7348ca0580f7cd3a956fdd7ed0b5080/time_machine-3.1.0.tar.gz", hash = "sha256:90831c2cf9e18e4199abb85fafa0c0ca0c6c15d0894a03ef68d5005a796c4f27", size = 14436, upload-time = "2025-11-21T13:56:33.802Z" }
wheels = [
@@ -1692,7 +1692,7 @@ name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "typing-extensions", marker = "extra == 'group-9-stagehand-pydantic-v2' or extra != 'group-9-stagehand-pydantic-v1'" },
+ { name = "typing-extensions", marker = "extra == 'group-15-stagehand-alpha-pydantic-v2' or extra != 'group-15-stagehand-alpha-pydantic-v1'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
From 22dd68831f5b599dc070798bb991b349211631d9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 29 Jan 2026 21:23:05 +0000
Subject: [PATCH 2/9] feat: Add executionModel serialization to api client
---
README.md | 2 +-
src/stagehand/types/session_execute_params.py | 16 ++++++++++-
tests/api_resources/test_sessions.py | 28 +++++++++++++++++++
3 files changed, 44 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index a88efa73..1a19352a 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@
diff --git a/src/stagehand/types/session_execute_params.py b/src/stagehand/types/session_execute_params.py
index 7a702945..d1afa802 100644
--- a/src/stagehand/types/session_execute_params.py
+++ b/src/stagehand/types/session_execute_params.py
@@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Union, Optional
-from typing_extensions import Literal, Required, Annotated, TypedDict
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
from .._utils import PropertyInfo
from .model_config_param import ModelConfigParam
@@ -11,6 +11,8 @@
__all__ = [
"SessionExecuteParamsBase",
"AgentConfig",
+ "AgentConfigExecutionModel",
+ "AgentConfigModel",
"ExecuteOptions",
"SessionExecuteParamsNonStreaming",
"SessionExecuteParamsStreaming",
@@ -32,6 +34,11 @@ class SessionExecuteParamsBase(TypedDict, total=False):
"""Whether to stream the response via SSE"""
+AgentConfigExecutionModel: TypeAlias = Union[ModelConfigParam, str]
+
+AgentConfigModel: TypeAlias = Union[ModelConfigParam, str]
+
+
class AgentConfig(TypedDict, total=False):
cua: bool
"""Deprecated.
@@ -39,6 +46,13 @@ class AgentConfig(TypedDict, total=False):
Use mode: 'cua' instead. If both are provided, mode takes precedence.
"""
+ execution_model: Annotated[AgentConfigExecutionModel, PropertyInfo(alias="executionModel")]
+ """
+ Model configuration object or model name string (e.g., 'openai/gpt-5-nano') for
+ tool execution (observe/act calls within agent tools). If not specified,
+ inherits from the main model configuration.
+ """
+
mode: Literal["dom", "hybrid", "cua"]
"""Tool mode for the agent (dom, hybrid, cua). If set, overrides cua."""
diff --git a/tests/api_resources/test_sessions.py b/tests/api_resources/test_sessions.py
index 5a843fd2..bd5d6c7c 100644
--- a/tests/api_resources/test_sessions.py
+++ b/tests/api_resources/test_sessions.py
@@ -232,6 +232,13 @@ def test_method_execute_with_all_params_overload_1(self, client: Stagehand) -> N
"provider": "openai",
},
"mode": "cua",
+ "model": {
+ "model_name": "openai/gpt-5-nano",
+ "api_key": "sk-some-openai-api-key",
+ "base_url": "https://api.openai.com/v1",
+ "provider": "openai",
+ },
+ "mode": "cua",
"model": {"model_name": "openai/gpt-5-nano"},
"provider": "openai",
"system_prompt": "systemPrompt",
@@ -321,6 +328,13 @@ def test_method_execute_with_all_params_overload_2(self, client: Stagehand) -> N
"provider": "openai",
},
"mode": "cua",
+ "model": {
+ "model_name": "openai/gpt-5-nano",
+ "api_key": "sk-some-openai-api-key",
+ "base_url": "https://api.openai.com/v1",
+ "provider": "openai",
+ },
+ "mode": "cua",
"model": {"model_name": "openai/gpt-5-nano"},
"provider": "openai",
"system_prompt": "systemPrompt",
@@ -1077,6 +1091,13 @@ async def test_method_execute_with_all_params_overload_1(self, async_client: Asy
"provider": "openai",
},
"mode": "cua",
+ "model": {
+ "model_name": "openai/gpt-5-nano",
+ "api_key": "sk-some-openai-api-key",
+ "base_url": "https://api.openai.com/v1",
+ "provider": "openai",
+ },
+ "mode": "cua",
"model": {"model_name": "openai/gpt-5-nano"},
"provider": "openai",
"system_prompt": "systemPrompt",
@@ -1166,6 +1187,13 @@ async def test_method_execute_with_all_params_overload_2(self, async_client: Asy
"provider": "openai",
},
"mode": "cua",
+ "model": {
+ "model_name": "openai/gpt-5-nano",
+ "api_key": "sk-some-openai-api-key",
+ "base_url": "https://api.openai.com/v1",
+ "provider": "openai",
+ },
+ "mode": "cua",
"model": {"model_name": "openai/gpt-5-nano"},
"provider": "openai",
"system_prompt": "systemPrompt",
From f9017c8fff8c58992739c6924ed6efbae552e027 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 30 Jan 2026 04:54:33 +0000
Subject: [PATCH 3/9] feat(client): add custom JSON encoder for extended type
support
---
src/stagehand/_base_client.py | 7 +-
src/stagehand/_compat.py | 6 +-
src/stagehand/_utils/_json.py | 35 ++++++++++
tests/test_utils/test_json.py | 126 ++++++++++++++++++++++++++++++++++
4 files changed, 169 insertions(+), 5 deletions(-)
create mode 100644 src/stagehand/_utils/_json.py
create mode 100644 tests/test_utils/test_json.py
diff --git a/src/stagehand/_base_client.py b/src/stagehand/_base_client.py
index 0f401f90..951feb82 100644
--- a/src/stagehand/_base_client.py
+++ b/src/stagehand/_base_client.py
@@ -86,6 +86,7 @@
APIConnectionError,
APIResponseValidationError,
)
+from ._utils._json import openapi_dumps
log: logging.Logger = logging.getLogger(__name__)
@@ -554,8 +555,10 @@ def _build_request(
kwargs["content"] = options.content
elif isinstance(json_data, bytes):
kwargs["content"] = json_data
- else:
- kwargs["json"] = json_data if is_given(json_data) else None
+ elif not files:
+ # Don't set content when JSON is sent as multipart/form-data,
+ # since httpx's content param overrides other body arguments
+ kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
kwargs["files"] = files
else:
headers.pop("Content-Type", None)
diff --git a/src/stagehand/_compat.py b/src/stagehand/_compat.py
index bdef67f0..786ff42a 100644
--- a/src/stagehand/_compat.py
+++ b/src/stagehand/_compat.py
@@ -139,6 +139,7 @@ def model_dump(
exclude_defaults: bool = False,
warnings: bool = True,
mode: Literal["json", "python"] = "python",
+ by_alias: bool | None = None,
) -> dict[str, Any]:
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
return model.model_dump(
@@ -148,13 +149,12 @@ def model_dump(
exclude_defaults=exclude_defaults,
# warnings are not supported in Pydantic v1
warnings=True if PYDANTIC_V1 else warnings,
+ by_alias=by_alias,
)
return cast(
"dict[str, Any]",
model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
- exclude=exclude,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
+ exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
),
)
diff --git a/src/stagehand/_utils/_json.py b/src/stagehand/_utils/_json.py
new file mode 100644
index 00000000..60584214
--- /dev/null
+++ b/src/stagehand/_utils/_json.py
@@ -0,0 +1,35 @@
+import json
+from typing import Any
+from datetime import datetime
+from typing_extensions import override
+
+import pydantic
+
+from .._compat import model_dump
+
+
+def openapi_dumps(obj: Any) -> bytes:
+ """
+ Serialize an object to UTF-8 encoded JSON bytes.
+
+ Extends the standard json.dumps with support for additional types
+ commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
+ """
+ return json.dumps(
+ obj,
+ cls=_CustomEncoder,
+ # Uses the same defaults as httpx's JSON serialization
+ ensure_ascii=False,
+ separators=(",", ":"),
+ allow_nan=False,
+ ).encode()
+
+
+class _CustomEncoder(json.JSONEncoder):
+ @override
+ def default(self, o: Any) -> Any:
+ if isinstance(o, datetime):
+ return o.isoformat()
+ if isinstance(o, pydantic.BaseModel):
+ return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
+ return super().default(o)
diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py
new file mode 100644
index 00000000..9f8a2e56
--- /dev/null
+++ b/tests/test_utils/test_json.py
@@ -0,0 +1,126 @@
+from __future__ import annotations
+
+import datetime
+from typing import Union
+
+import pydantic
+
+from stagehand import _compat
+from stagehand._utils._json import openapi_dumps
+
+
+class TestOpenapiDumps:
+ def test_basic(self) -> None:
+ data = {"key": "value", "number": 42}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"key":"value","number":42}'
+
+ def test_datetime_serialization(self) -> None:
+ dt = datetime.datetime(2023, 1, 1, 12, 0, 0)
+ data = {"datetime": dt}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}'
+
+ def test_pydantic_model_serialization(self) -> None:
+ class User(pydantic.BaseModel):
+ first_name: str
+ last_name: str
+ age: int
+
+ model_instance = User(first_name="John", last_name="Kramer", age=83)
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}'
+
+ def test_pydantic_model_with_default_values(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ role: str = "user"
+ active: bool = True
+ score: int = 0
+
+ model_instance = User(name="Alice")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Alice"}}'
+
+ def test_pydantic_model_with_default_values_overridden(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ role: str = "user"
+ active: bool = True
+
+ model_instance = User(name="Bob", role="admin", active=False)
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}'
+
+ def test_pydantic_model_with_alias(self) -> None:
+ class User(pydantic.BaseModel):
+ first_name: str = pydantic.Field(alias="firstName")
+ last_name: str = pydantic.Field(alias="lastName")
+
+ model_instance = User(firstName="John", lastName="Doe")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}'
+
+ def test_pydantic_model_with_alias_and_default(self) -> None:
+ class User(pydantic.BaseModel):
+ user_name: str = pydantic.Field(alias="userName")
+ user_role: str = pydantic.Field(default="member", alias="userRole")
+ is_active: bool = pydantic.Field(default=True, alias="isActive")
+
+ model_instance = User(userName="charlie")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"userName":"charlie"}}'
+
+ model_with_overrides = User(userName="diana", userRole="admin", isActive=False)
+ data = {"model": model_with_overrides}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}'
+
+ def test_pydantic_model_with_nested_models_and_defaults(self) -> None:
+ class Address(pydantic.BaseModel):
+ street: str
+ city: str = "Unknown"
+
+ class User(pydantic.BaseModel):
+ name: str
+ address: Address
+ verified: bool = False
+
+ if _compat.PYDANTIC_V1:
+ # to handle forward references in Pydantic v1
+ User.update_forward_refs(**locals()) # type: ignore[reportDeprecated]
+
+ address = Address(street="123 Main St")
+ user = User(name="Diana", address=address)
+ data = {"user": user}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}'
+
+ address_with_city = Address(street="456 Oak Ave", city="Boston")
+ user_verified = User(name="Eve", address=address_with_city, verified=True)
+ data = {"user": user_verified}
+ json_bytes = openapi_dumps(data)
+ assert (
+ json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}'
+ )
+
+ def test_pydantic_model_with_optional_fields(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ email: Union[str, None]
+ phone: Union[str, None]
+
+ model_with_none = User(name="Eve", email=None, phone=None)
+ data = {"model": model_with_none}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}'
+
+ model_with_values = User(name="Frank", email="frank@example.com", phone=None)
+ data = {"model": model_with_values}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}'
From 0061b08bc5a96960c58e02305b4a69519ede45ff Mon Sep 17 00:00:00 2001
From: monadoid
Local development
diff --git a/examples/playwright_page_example.py b/examples/playwright_page_example.py
deleted file mode 100644
index 563cda09..00000000
--- a/examples/playwright_page_example.py
+++ /dev/null
@@ -1,117 +0,0 @@
-"""
-Example: use a Playwright Page with the Stagehand Python SDK.
-
-What this demonstrates:
-- Start a Stagehand session (remote Stagehand API / Browserbase browser)
-- Attach Playwright to the same browser via CDP (`cdp_url`)
-- Pass the Playwright `page` into `session.observe/act/extract` so Stagehand
- auto-detects the correct `frame_id` for that page.
-
-Environment variables required:
-- MODEL_API_KEY
-- BROWSERBASE_API_KEY
-- BROWSERBASE_PROJECT_ID
-
-Optional:
-- STAGEHAND_BASE_URL (defaults to https://api.stagehand.browserbase.com)
-"""
-
-from __future__ import annotations
-
-import os
-import sys
-from typing import Optional
-
-from stagehand import Stagehand
-
-
-def main() -> None:
- model_api_key = os.environ.get("MODEL_API_KEY")
- if not model_api_key:
- sys.exit("Set the MODEL_API_KEY environment variable to run this example.")
-
- bb_api_key = os.environ.get("BROWSERBASE_API_KEY")
- bb_project_id = os.environ.get("BROWSERBASE_PROJECT_ID")
- if not bb_api_key or not bb_project_id:
- sys.exit(
- "Set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID to run this example."
- )
-
- try:
- from playwright.sync_api import sync_playwright # type: ignore[import-not-found]
- except Exception:
- sys.exit(
- "Playwright is not installed. Install it with:\n"
- " uv pip install playwright\n"
- "and ensure browsers are installed (e.g. `playwright install chromium`)."
- )
-
- session_id: Optional[str] = None
-
- with Stagehand(
- server="remote",
- browserbase_api_key=bb_api_key,
- browserbase_project_id=bb_project_id,
- model_api_key=model_api_key,
- ) as client:
- print("⏳ Starting Stagehand session...")
- session = client.sessions.start(
- model_name="openai/gpt-5-nano",
- browser={"type": "browserbase"},
- )
- session_id = session.id
-
- cdp_url = session.data.cdp_url
- if not cdp_url:
- sys.exit(
- "No cdp_url returned from the API for this session; cannot attach Playwright."
- )
-
- print(f"✅ Session started: {session_id}")
- print("🔌 Connecting Playwright to the same browser over CDP...")
-
- with sync_playwright() as p:
- # Attach to the same browser session Stagehand is controlling.
- browser = p.chromium.connect_over_cdp(cdp_url)
- try:
- # Reuse an existing context/page if present; otherwise create one.
- context = browser.contexts[0] if browser.contexts else browser.new_context()
- page = context.pages[0] if context.pages else context.new_page()
-
- page.goto("https://example.com", wait_until="domcontentloaded")
-
- print("👀 Stagehand.observe(page=...) ...")
- actions = session.observe(
- instruction="Find the most relevant click target on this page",
- page=page,
- )
- print(f"Observed {len(actions.data.result)} actions")
-
- print("🧠 Stagehand.extract(page=...) ...")
- extracted = session.extract(
- instruction="Extract the page title and the primary heading (h1) text",
- schema={
- "type": "object",
- "properties": {
- "title": {"type": "string"},
- "h1": {"type": "string"},
- },
- "required": ["title", "h1"],
- "additionalProperties": False,
- },
- page=page,
- )
- print("Extracted:", extracted.data.result)
-
- print("🖱️ Stagehand.act(page=...) ...")
- _ = session.act(
- input="Click the 'Learn more' link",
- page=page,
- )
- print("Done.")
- finally:
- browser.close()
-
-
-if __name__ == "__main__":
- main()
diff --git a/examples/remote_browser_playwright_example.py b/examples/remote_browser_playwright_example.py
new file mode 100644
index 00000000..27853c50
--- /dev/null
+++ b/examples/remote_browser_playwright_example.py
@@ -0,0 +1,170 @@
+"""
+Example: use a Playwright Page with the Stagehand Python SDK (remote Browserbase).
+
+What this demonstrates:
+- Start a Stagehand session (remote Stagehand API / Browserbase browser)
+- Attach Playwright to the same browser via CDP (`cdp_url`)
+- Pass the Playwright `page` into `session.observe/act/extract/execute`
+ so Stagehand auto-detects the correct `frame_id` for that page
+- Stream SSE events by default for observe/act/extract/execute
+- Run the full flow: start → observe → act → extract → agent/execute → end
+
+Environment variables required:
+- MODEL_API_KEY
+- BROWSERBASE_API_KEY
+- BROWSERBASE_PROJECT_ID
+
+Optional:
+- STAGEHAND_BASE_URL (defaults to https://api.stagehand.browserbase.com)
+"""
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Any, Optional
+
+from stagehand import Stagehand
+
+
+def _print_stream_events(stream: Any, label: str) -> object | None:
+ result_payload: object | None = None
+ for event in stream:
+ if event.type == "log":
+ print(f"[{label}][log] {event.data.message}")
+ continue
+
+ status = event.data.status
+ print(f"[{label}][system] status={status}")
+ if status == "finished":
+ result_payload = event.data.result
+ elif status == "error":
+ error_message = event.data.error or "unknown error"
+ raise RuntimeError(f"{label} stream reported error: {error_message}")
+
+ return result_payload
+
+
+def main() -> None:
+ model_api_key = os.environ.get("MODEL_API_KEY")
+ if not model_api_key:
+ sys.exit("Set the MODEL_API_KEY environment variable to run this example.")
+
+ bb_api_key = os.environ.get("BROWSERBASE_API_KEY")
+ bb_project_id = os.environ.get("BROWSERBASE_PROJECT_ID")
+ if not bb_api_key or not bb_project_id:
+ sys.exit(
+ "Set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID to run this example."
+ )
+
+ try:
+ from playwright.sync_api import sync_playwright # type: ignore[import-not-found]
+ except Exception:
+ sys.exit(
+ "Playwright is not installed. Install it with:\n"
+ " uv pip install playwright\n"
+ "and ensure browsers are installed (e.g. `playwright install chromium`)."
+ )
+
+ session_id: Optional[str] = None
+
+ with Stagehand(
+ server="remote",
+ browserbase_api_key=bb_api_key,
+ browserbase_project_id=bb_project_id,
+ model_api_key=model_api_key,
+ ) as client:
+ print("⏳ Starting Stagehand session...")
+ session = client.sessions.start(
+ model_name="openai/gpt-5-nano",
+ browser={"type": "browserbase"},
+ verbose=2,
+ )
+ session_id = session.id
+
+ cdp_url = session.data.cdp_url
+ if not cdp_url:
+ sys.exit(
+ "No cdp_url returned from the API for this session; cannot attach Playwright."
+ )
+
+ print(f"✅ Session started: {session_id}")
+ print("🔌 Connecting Playwright to the same browser over CDP...")
+
+ try:
+ with sync_playwright() as p:
+ # Attach to the same browser session Stagehand is controlling.
+ browser = p.chromium.connect_over_cdp(cdp_url)
+ try:
+ # Reuse an existing context/page if present; otherwise create one.
+ context = browser.contexts[0] if browser.contexts else browser.new_context()
+ page = context.pages[0] if context.pages else context.new_page()
+
+ page.goto("https://example.com", wait_until="domcontentloaded")
+
+ print("👀 Stagehand.observe(page=...) with SSE streaming...")
+ observe_stream = session.observe(
+ instruction="Find the most relevant click target on this page",
+ page=page,
+ stream_response=True,
+ x_stream_response="true",
+ )
+ observe_result = _print_stream_events(observe_stream, "observe")
+
+ actions = observe_result or []
+ if not actions:
+ print("No actions found; ending session.")
+ return
+
+ print("🖱️ Stagehand.act(page=...) with SSE streaming...")
+ act_stream = session.act(
+ input=actions[0],
+ page=page,
+ stream_response=True,
+ x_stream_response="true",
+ )
+ _ = _print_stream_events(act_stream, "act")
+
+ print("🧠 Stagehand.extract(page=...) with SSE streaming...")
+ extract_stream = session.extract(
+ instruction="Extract the page title and the primary heading (h1) text",
+ schema={
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "h1": {"type": "string"},
+ },
+ "required": ["title", "h1"],
+ "additionalProperties": False,
+ },
+ page=page,
+ stream_response=True,
+ x_stream_response="true",
+ )
+ extracted = _print_stream_events(extract_stream, "extract")
+ print("Extracted:", extracted)
+
+ print("🤖 Stagehand.execute(page=...) with SSE streaming...")
+ execute_stream = session.execute(
+ agent_config={"model": "openai/gpt-5-nano"},
+ execute_options={
+ "instruction": (
+ "Open the 'Learn more' link if present and summarize the destination in one sentence."
+ ),
+ "max_steps": 5,
+ },
+ page=page,
+ stream_response=True,
+ x_stream_response="true",
+ )
+ execute_result = _print_stream_events(execute_stream, "execute")
+ print("Execute result:", execute_result)
+ finally:
+ browser.close()
+ finally:
+ session.end()
+ print("✅ Session ended.")
+
+
+if __name__ == "__main__":
+ main()
From 60b87871ad8eb74b53cd214ea285cd5a26c96512 Mon Sep 17 00:00:00 2001
From: monadoid Local development
diff --git a/examples/local_browser_playwright_example.py b/examples/local_browser_playwright_example.py
new file mode 100644
index 00000000..12b65585
--- /dev/null
+++ b/examples/local_browser_playwright_example.py
@@ -0,0 +1,170 @@
+"""
+Example: use a Playwright Page with the Stagehand Python SDK (local browser).
+
+What this demonstrates:
+- Start a Stagehand session in local mode
+- Launch a local Playwright browser server and share its CDP URL with Stagehand
+- Pass the Playwright `page` into `session.observe/act/extract/execute`
+ so Stagehand auto-detects the correct `frame_id` for that page
+- Stream SSE events by default for observe/act/extract/execute
+- Run the full flow: start → observe → act → extract → agent/execute → end
+
+Environment variables required:
+- MODEL_API_KEY
+- BROWSERBASE_API_KEY (can be any value in local mode)
+- BROWSERBASE_PROJECT_ID (can be any value in local mode)
+
+Optional:
+- STAGEHAND_BASE_URL (defaults to http://127.0.0.1:3000)
+"""
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Any, Optional
+
+from stagehand import Stagehand
+
+
+def _print_stream_events(stream: Any, label: str) -> object | None:
+ result_payload: object | None = None
+ for event in stream:
+ if event.type == "log":
+ print(f"[{label}][log] {event.data.message}")
+ continue
+
+ status = event.data.status
+ print(f"[{label}][system] status={status}")
+ if status == "finished":
+ result_payload = event.data.result
+ elif status == "error":
+ error_message = event.data.error or "unknown error"
+ raise RuntimeError(f"{label} stream reported error: {error_message}")
+
+ return result_payload
+
+
+def main() -> None:
+ model_api_key = os.environ.get("MODEL_API_KEY")
+ if not model_api_key:
+ sys.exit("Set the MODEL_API_KEY environment variable to run this example.")
+
+ bb_api_key = os.environ.get("BROWSERBASE_API_KEY")
+ bb_project_id = os.environ.get("BROWSERBASE_PROJECT_ID")
+ if not bb_api_key or not bb_project_id:
+ sys.exit(
+ "Set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID to run this example."
+ )
+
+ try:
+ from playwright.sync_api import sync_playwright # type: ignore[import-not-found]
+ except Exception:
+ sys.exit(
+ "Playwright is not installed. Install it with:\n"
+ " uv pip install playwright\n"
+ "and ensure browsers are installed (e.g. `playwright install chromium`)."
+ )
+
+ session_id: Optional[str] = None
+
+ with Stagehand(
+ server="local",
+ browserbase_api_key=bb_api_key,
+ browserbase_project_id=bb_project_id,
+ model_api_key=model_api_key,
+ local_openai_api_key=model_api_key,
+ local_ready_timeout_s=30.0,
+ ) as client:
+ print("⏳ Starting Stagehand session (local server + local browser)...")
+
+ with sync_playwright() as p:
+ browser_server = p.chromium.launch_server(headless=True)
+ cdp_url = browser_server.ws_endpoint
+
+ session = client.sessions.start(
+ model_name="openai/gpt-5-nano",
+ browser={
+ "type": "local",
+ "launchOptions": {"cdpUrl": cdp_url},
+ },
+ verbose=2,
+ )
+ session_id = session.id
+
+ print(f"✅ Session started: {session_id}")
+ print("🔌 Connecting Playwright to the same browser over CDP...")
+
+ browser = p.chromium.connect_over_cdp(cdp_url)
+ try:
+ context = browser.contexts[0] if browser.contexts else browser.new_context()
+ page = context.pages[0] if context.pages else context.new_page()
+
+ page.goto("https://example.com", wait_until="domcontentloaded")
+
+ print("👀 Stagehand.observe(page=...) with SSE streaming...")
+ observe_stream = session.observe(
+ instruction="Find the most relevant click target on this page",
+ page=page,
+ stream_response=True,
+ x_stream_response="true",
+ )
+ observe_result = _print_stream_events(observe_stream, "observe")
+
+ actions = observe_result or []
+ if not actions:
+ print("No actions found; ending session.")
+ return
+
+ print("🖱️ Stagehand.act(page=...) with SSE streaming...")
+ act_stream = session.act(
+ input=actions[0],
+ page=page,
+ stream_response=True,
+ x_stream_response="true",
+ )
+ _ = _print_stream_events(act_stream, "act")
+
+ print("🧠 Stagehand.extract(page=...) with SSE streaming...")
+ extract_stream = session.extract(
+ instruction="Extract the page title and the primary heading (h1) text",
+ schema={
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "h1": {"type": "string"},
+ },
+ "required": ["title", "h1"],
+ "additionalProperties": False,
+ },
+ page=page,
+ stream_response=True,
+ x_stream_response="true",
+ )
+ extracted = _print_stream_events(extract_stream, "extract")
+ print("Extracted:", extracted)
+
+ print("🤖 Stagehand.execute(page=...) with SSE streaming...")
+ execute_stream = session.execute(
+ agent_config={"model": "openai/gpt-5-nano"},
+ execute_options={
+ "instruction": (
+ "Open the 'Learn more' link if present and summarize the destination in one sentence."
+ ),
+ "max_steps": 5,
+ },
+ page=page,
+ stream_response=True,
+ x_stream_response="true",
+ )
+ execute_result = _print_stream_events(execute_stream, "execute")
+ print("Execute result:", execute_result)
+ finally:
+ browser.close()
+ browser_server.close()
+ session.end()
+ print("✅ Session ended.")
+
+
+if __name__ == "__main__":
+ main()
From aa3fbd46cfd8e3ad4f4db6724c14d43db52564b6 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 6 Feb 2026 22:50:53 +0000
Subject: [PATCH 8/9] chore(internal): codegen related update
---
README.md | 4 +-
examples/playwright_page_example.py | 117 ++++++++++++++++++++++++++++
2 files changed, 119 insertions(+), 2 deletions(-)
create mode 100644 examples/playwright_page_example.py
diff --git a/README.md b/README.md
index 42343820..ac3c5583 100644
--- a/README.md
+++ b/README.md
@@ -622,9 +622,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`
diff --git a/examples/playwright_page_example.py b/examples/playwright_page_example.py
new file mode 100644
index 00000000..563cda09
--- /dev/null
+++ b/examples/playwright_page_example.py
@@ -0,0 +1,117 @@
+"""
+Example: use a Playwright Page with the Stagehand Python SDK.
+
+What this demonstrates:
+- Start a Stagehand session (remote Stagehand API / Browserbase browser)
+- Attach Playwright to the same browser via CDP (`cdp_url`)
+- Pass the Playwright `page` into `session.observe/act/extract` so Stagehand
+ auto-detects the correct `frame_id` for that page.
+
+Environment variables required:
+- MODEL_API_KEY
+- BROWSERBASE_API_KEY
+- BROWSERBASE_PROJECT_ID
+
+Optional:
+- STAGEHAND_BASE_URL (defaults to https://api.stagehand.browserbase.com)
+"""
+
+from __future__ import annotations
+
+import os
+import sys
+from typing import Optional
+
+from stagehand import Stagehand
+
+
+def main() -> None:
+ model_api_key = os.environ.get("MODEL_API_KEY")
+ if not model_api_key:
+ sys.exit("Set the MODEL_API_KEY environment variable to run this example.")
+
+ bb_api_key = os.environ.get("BROWSERBASE_API_KEY")
+ bb_project_id = os.environ.get("BROWSERBASE_PROJECT_ID")
+ if not bb_api_key or not bb_project_id:
+ sys.exit(
+ "Set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID to run this example."
+ )
+
+ try:
+ from playwright.sync_api import sync_playwright # type: ignore[import-not-found]
+ except Exception:
+ sys.exit(
+ "Playwright is not installed. Install it with:\n"
+ " uv pip install playwright\n"
+ "and ensure browsers are installed (e.g. `playwright install chromium`)."
+ )
+
+ session_id: Optional[str] = None
+
+ with Stagehand(
+ server="remote",
+ browserbase_api_key=bb_api_key,
+ browserbase_project_id=bb_project_id,
+ model_api_key=model_api_key,
+ ) as client:
+ print("⏳ Starting Stagehand session...")
+ session = client.sessions.start(
+ model_name="openai/gpt-5-nano",
+ browser={"type": "browserbase"},
+ )
+ session_id = session.id
+
+ cdp_url = session.data.cdp_url
+ if not cdp_url:
+ sys.exit(
+ "No cdp_url returned from the API for this session; cannot attach Playwright."
+ )
+
+ print(f"✅ Session started: {session_id}")
+ print("🔌 Connecting Playwright to the same browser over CDP...")
+
+ with sync_playwright() as p:
+ # Attach to the same browser session Stagehand is controlling.
+ browser = p.chromium.connect_over_cdp(cdp_url)
+ try:
+ # Reuse an existing context/page if present; otherwise create one.
+ context = browser.contexts[0] if browser.contexts else browser.new_context()
+ page = context.pages[0] if context.pages else context.new_page()
+
+ page.goto("https://example.com", wait_until="domcontentloaded")
+
+ print("👀 Stagehand.observe(page=...) ...")
+ actions = session.observe(
+ instruction="Find the most relevant click target on this page",
+ page=page,
+ )
+ print(f"Observed {len(actions.data.result)} actions")
+
+ print("🧠 Stagehand.extract(page=...) ...")
+ extracted = session.extract(
+ instruction="Extract the page title and the primary heading (h1) text",
+ schema={
+ "type": "object",
+ "properties": {
+ "title": {"type": "string"},
+ "h1": {"type": "string"},
+ },
+ "required": ["title", "h1"],
+ "additionalProperties": False,
+ },
+ page=page,
+ )
+ print("Extracted:", extracted.data.result)
+
+ print("🖱️ Stagehand.act(page=...) ...")
+ _ = session.act(
+ input="Click the 'Learn more' link",
+ page=page,
+ )
+ print("Done.")
+ finally:
+ browser.close()
+
+
+if __name__ == "__main__":
+ main()
From 45689ec6c41073e965348d73cd1129d6c4aeb6a8 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 6 Feb 2026 22:51:14 +0000
Subject: [PATCH 9/9] release: 3.6.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 16 ++++++++++++++++
pyproject.toml | 2 +-
src/stagehand/_version.py | 2 +-
4 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 3cf104e9..dc703804 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "3.5.0"
+ ".": "3.6.0"
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aba93aab..8ae675d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Changelog
+## 3.6.0 (2026-02-06)
+
+Full Changelog: [v3.5.0...v3.6.0](https://github.com/browserbase/stagehand-python/compare/v3.5.0...v3.6.0)
+
+### Features
+
+* Add executionModel serialization to api client ([22dd688](https://github.com/browserbase/stagehand-python/commit/22dd68831f5b599dc070798bb991b349211631d9))
+* **client:** add custom JSON encoder for extended type support ([f9017c8](https://github.com/browserbase/stagehand-python/commit/f9017c8fff8c58992739c6924ed6efbae552e027))
+
+
+### Chores
+
+* **internal:** codegen related update ([aa3fbd4](https://github.com/browserbase/stagehand-python/commit/aa3fbd46cfd8e3ad4f4db6724c14d43db52564b6))
+* **internal:** codegen related update ([555a9c4](https://github.com/browserbase/stagehand-python/commit/555a9c44a902a6735e585e09ed974d9a7915a6bb))
+* sync repo ([0c9bb8c](https://github.com/browserbase/stagehand-python/commit/0c9bb8cb3b791bf8c60ad0065fed9ad16b912b8e))
+
## 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)
diff --git a/pyproject.toml b/pyproject.toml
index 3ee76886..95783008 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "stagehand"
-version = "3.5.0"
+version = "3.6.0"
description = "The official Python library for the stagehand API"
dynamic = ["readme"]
license = "MIT"
diff --git a/src/stagehand/_version.py b/src/stagehand/_version.py
index 33317dc3..914bc189 100644
--- a/src/stagehand/_version.py
+++ b/src/stagehand/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "stagehand"
-__version__ = "3.5.0" # x-release-please-version
+__version__ = "3.6.0" # x-release-please-version