From b2cccf56bc7e99d7869b8ed9339956a9f160348a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:52:23 +0000 Subject: [PATCH 1/3] feat(internal): implement indices array format for query and form serialization --- src/stagehand/_qs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/stagehand/_qs.py b/src/stagehand/_qs.py index ada6fd3f..de8c99bc 100644 --- a/src/stagehand/_qs.py +++ b/src/stagehand/_qs.py @@ -101,7 +101,10 @@ def _stringify_item( items.extend(self._stringify_item(key, item, opts)) return items elif array_format == "indices": - raise NotImplementedError("The array indices format is not supported yet") + items = [] + for i, item in enumerate(value): + items.extend(self._stringify_item(f"{key}[{i}]", item, opts)) + return items elif array_format == "brackets": items = [] key = key + "[]" From 7acdea79af8a55b1af05a4154161f6aafde6b5b9 Mon Sep 17 00:00:00 2001 From: monadoid Date: Fri, 27 Mar 2026 15:57:20 +0100 Subject: [PATCH 2/3] STG-1537 default local-mode sessions to local browser --- src/stagehand/resources/sessions_helpers.py | 6 +++ tests/test_local_server.py | 41 +++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/stagehand/resources/sessions_helpers.py b/src/stagehand/resources/sessions_helpers.py index 8eafd75e..7e979d8e 100644 --- a/src/stagehand/resources/sessions_helpers.py +++ b/src/stagehand/resources/sessions_helpers.py @@ -83,6 +83,9 @@ def start( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Session: + if browser is omit and getattr(self._client, "_server_mode", None) == "local": + browser = {"type": "local"} + start_response = super().start( model_name=model_name, act_timeout_ms=act_timeout_ms, @@ -136,6 +139,9 @@ async def start( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncSession: + if browser is omit and getattr(self._client, "_server_mode", None) == "local": + browser = {"type": "local"} + start_response: SessionStartResponse = await super().start( model_name=model_name, act_timeout_ms=act_timeout_ms, diff --git a/tests/test_local_server.py b/tests/test_local_server.py index 0f6dcd2a..ebf44686 100644 --- a/tests/test_local_server.py +++ b/tests/test_local_server.py @@ -1,5 +1,7 @@ from __future__ import annotations +import json + import httpx import pytest from respx import MockRouter @@ -67,6 +69,45 @@ def test_sync_local_mode_starts_before_first_request(respx_mock: MockRouter, mon assert dummy.closed == 1 +@pytest.mark.respx(base_url="http://127.0.0.1:43127") +def test_sync_local_mode_defaults_browser_type_local_when_omitted( + respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch +) -> None: + _set_required_env(monkeypatch) + + dummy = _DummySeaServer("http://127.0.0.1:43127") + request_body: dict[str, object] = {} + + def _capture_start_request(request: httpx.Request) -> httpx.Response: + request_body["json"] = json.loads(request.content.decode("utf-8")) + return httpx.Response( + 200, + json={ + "success": True, + "data": { + "available": True, + "connectUrl": "ws://example", + "sessionId": "00000000-0000-0000-0000-000000000001", + }, + }, + ) + + respx_mock.post("/v1/sessions/start").mock(side_effect=_capture_start_request) + + client = Stagehand(server="local", _local_stagehand_binary_path="/does/not/matter/in/test") + client._sea_server = dummy # type: ignore[attr-defined] + + resp = client.sessions.start(model_name="openai/gpt-5-nano") + assert resp.success is True + assert request_body["json"] == { + "modelName": "openai/gpt-5-nano", + "browser": {"type": "local"}, + } + + client.close() + assert dummy.closed == 1 + + @pytest.mark.respx(base_url="http://127.0.0.1:43124") @pytest.mark.asyncio async def test_async_local_mode_starts_before_first_request( From 56d7bf2c130cfd61c011aa204407e715070fdde9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:02:53 +0000 Subject: [PATCH 3/3] release: 3.19.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- src/stagehand/_version.py | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index da0b97f9..60a50ee6 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.18.0" + ".": "3.19.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index c67dde45..faebfb36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 3.19.0 (2026-03-27) + +Full Changelog: [v3.18.0...v3.19.0](https://github.com/browserbase/stagehand-python/compare/v3.18.0...v3.19.0) + +### Features + +* **internal:** implement indices array format for query and form serialization ([b2cccf5](https://github.com/browserbase/stagehand-python/commit/b2cccf56bc7e99d7869b8ed9339956a9f160348a)) + ## 3.18.0 (2026-03-25) Full Changelog: [v3.7.0...v3.18.0](https://github.com/browserbase/stagehand-python/compare/v3.7.0...v3.18.0) diff --git a/pyproject.toml b/pyproject.toml index beb416c1..833c4605 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "stagehand" -version = "3.18.0" +version = "3.19.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 e5144c2d..e8cb2209 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.18.0" # x-release-please-version +__version__ = "3.19.0" # x-release-please-version