From 97f1c02a520d433abe467b11a04f7fbab85fd3f3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 21:47:54 +0000 Subject: [PATCH 1/7] feat: [AI-1993] - Surface structured JSON content on /v2/fetch --- .stats.yml | 8 +- api.md | 13 - .../resources/sessions/__init__.py | 14 - src/browserbase/resources/sessions/replays.py | 266 ------------------ .../resources/sessions/sessions.py | 32 --- .../types/fetch_api_create_response.py | 10 +- src/browserbase/types/sessions/__init__.py | 1 - .../sessions/replay_retrieve_response.py | 25 -- tests/api_resources/sessions/test_replays.py | 242 ---------------- 9 files changed, 11 insertions(+), 600 deletions(-) delete mode 100644 src/browserbase/resources/sessions/replays.py delete mode 100644 src/browserbase/types/sessions/replay_retrieve_response.py delete mode 100644 tests/api_resources/sessions/test_replays.py diff --git a/.stats.yml b/.stats.yml index 192797ac..523861e0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 23 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-466614a040e7f31307530bd6ba443e714b6303eaa141904e7d32e6641d5ec55f.yml -openapi_spec_hash: 2d06680e7c17847e4fbcac35124d2456 -config_hash: 40fbac80e24faaa0dc19e93368bcd821 +configured_endpoints: 21 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-1821faac6d1422fea15b3ba1f88c0f5f00c43464524e17d3fd1efd1ea148b7c4.yml +openapi_spec_hash: 1e3fba074314f557dc6973cf97ea6a69 +config_hash: cf04ecfb8dad5fbd8b85be25d6e9ec55 diff --git a/api.md b/api.md index 581574a3..b6066cb8 100644 --- a/api.md +++ b/api.md @@ -128,16 +128,3 @@ from browserbase.types.sessions import UploadCreateResponse Methods: - client.sessions.uploads.create(id, \*\*params) -> UploadCreateResponse - -## Replays - -Types: - -```python -from browserbase.types.sessions import ReplayRetrieveResponse -``` - -Methods: - -- client.sessions.replays.retrieve(id) -> ReplayRetrieveResponse -- client.sessions.replays.retrieve_page(page_id, \*, id) -> BinaryAPIResponse diff --git a/src/browserbase/resources/sessions/__init__.py b/src/browserbase/resources/sessions/__init__.py index e66ee0ce..b3877e12 100644 --- a/src/browserbase/resources/sessions/__init__.py +++ b/src/browserbase/resources/sessions/__init__.py @@ -8,14 +8,6 @@ LogsResourceWithStreamingResponse, AsyncLogsResourceWithStreamingResponse, ) -from .replays import ( - ReplaysResource, - AsyncReplaysResource, - ReplaysResourceWithRawResponse, - AsyncReplaysResourceWithRawResponse, - ReplaysResourceWithStreamingResponse, - AsyncReplaysResourceWithStreamingResponse, -) from .uploads import ( UploadsResource, AsyncUploadsResource, @@ -74,12 +66,6 @@ "AsyncUploadsResourceWithRawResponse", "UploadsResourceWithStreamingResponse", "AsyncUploadsResourceWithStreamingResponse", - "ReplaysResource", - "AsyncReplaysResource", - "ReplaysResourceWithRawResponse", - "AsyncReplaysResourceWithRawResponse", - "ReplaysResourceWithStreamingResponse", - "AsyncReplaysResourceWithStreamingResponse", "SessionsResource", "AsyncSessionsResource", "SessionsResourceWithRawResponse", diff --git a/src/browserbase/resources/sessions/replays.py b/src/browserbase/resources/sessions/replays.py deleted file mode 100644 index c9240356..00000000 --- a/src/browserbase/resources/sessions/replays.py +++ /dev/null @@ -1,266 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from ..._types import Body, Query, Headers, NotGiven, not_given -from ..._utils import path_template -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - BinaryAPIResponse, - AsyncBinaryAPIResponse, - StreamedBinaryAPIResponse, - AsyncStreamedBinaryAPIResponse, - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - to_custom_raw_response_wrapper, - async_to_streamed_response_wrapper, - to_custom_streamed_response_wrapper, - async_to_custom_raw_response_wrapper, - async_to_custom_streamed_response_wrapper, -) -from ..._base_client import make_request_options -from ...types.sessions.replay_retrieve_response import ReplayRetrieveResponse - -__all__ = ["ReplaysResource", "AsyncReplaysResource"] - - -class ReplaysResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> ReplaysResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/browserbase/sdk-python#accessing-raw-response-data-eg-headers - """ - return ReplaysResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ReplaysResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/browserbase/sdk-python#with_streaming_response - """ - return ReplaysResourceWithStreamingResponse(self) - - def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ReplayRetrieveResponse: - """ - Returns page metadata for a session replay, including timing information and the - URL of each page's HLS playlist. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - path_template("/v1/sessions/{id}/replays", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ReplayRetrieveResponse, - ) - - def retrieve_page( - self, - page_id: str, - *, - id: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> BinaryAPIResponse: - """ - Returns an HLS VOD media playlist (.m3u8) for a specific page of a session - replay. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - if not page_id: - raise ValueError(f"Expected a non-empty value for `page_id` but received {page_id!r}") - extra_headers = {"Accept": "application/vnd.apple.mpegurl", **(extra_headers or {})} - return self._get( - path_template("/v1/sessions/{id}/replays/{page_id}", id=id, page_id=page_id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=BinaryAPIResponse, - ) - - -class AsyncReplaysResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncReplaysResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/browserbase/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncReplaysResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncReplaysResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/browserbase/sdk-python#with_streaming_response - """ - return AsyncReplaysResourceWithStreamingResponse(self) - - async def retrieve( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ReplayRetrieveResponse: - """ - Returns page metadata for a session replay, including timing information and the - URL of each page's HLS playlist. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - path_template("/v1/sessions/{id}/replays", id=id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ReplayRetrieveResponse, - ) - - async def retrieve_page( - self, - page_id: str, - *, - id: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AsyncBinaryAPIResponse: - """ - Returns an HLS VOD media playlist (.m3u8) for a specific page of a session - replay. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - if not page_id: - raise ValueError(f"Expected a non-empty value for `page_id` but received {page_id!r}") - extra_headers = {"Accept": "application/vnd.apple.mpegurl", **(extra_headers or {})} - return await self._get( - path_template("/v1/sessions/{id}/replays/{page_id}", id=id, page_id=page_id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AsyncBinaryAPIResponse, - ) - - -class ReplaysResourceWithRawResponse: - def __init__(self, replays: ReplaysResource) -> None: - self._replays = replays - - self.retrieve = to_raw_response_wrapper( - replays.retrieve, - ) - self.retrieve_page = to_custom_raw_response_wrapper( - replays.retrieve_page, - BinaryAPIResponse, - ) - - -class AsyncReplaysResourceWithRawResponse: - def __init__(self, replays: AsyncReplaysResource) -> None: - self._replays = replays - - self.retrieve = async_to_raw_response_wrapper( - replays.retrieve, - ) - self.retrieve_page = async_to_custom_raw_response_wrapper( - replays.retrieve_page, - AsyncBinaryAPIResponse, - ) - - -class ReplaysResourceWithStreamingResponse: - def __init__(self, replays: ReplaysResource) -> None: - self._replays = replays - - self.retrieve = to_streamed_response_wrapper( - replays.retrieve, - ) - self.retrieve_page = to_custom_streamed_response_wrapper( - replays.retrieve_page, - StreamedBinaryAPIResponse, - ) - - -class AsyncReplaysResourceWithStreamingResponse: - def __init__(self, replays: AsyncReplaysResource) -> None: - self._replays = replays - - self.retrieve = async_to_streamed_response_wrapper( - replays.retrieve, - ) - self.retrieve_page = async_to_custom_streamed_response_wrapper( - replays.retrieve_page, - AsyncStreamedBinaryAPIResponse, - ) diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index a54d7a72..ce3de98f 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -16,14 +16,6 @@ AsyncLogsResourceWithStreamingResponse, ) from ...types import session_list_params, session_create_params, session_update_params -from .replays import ( - ReplaysResource, - AsyncReplaysResource, - ReplaysResourceWithRawResponse, - AsyncReplaysResourceWithRawResponse, - ReplaysResourceWithStreamingResponse, - AsyncReplaysResourceWithStreamingResponse, -) from .uploads import ( UploadsResource, AsyncUploadsResource, @@ -85,10 +77,6 @@ def recording(self) -> RecordingResource: def uploads(self) -> UploadsResource: return UploadsResource(self._client) - @cached_property - def replays(self) -> ReplaysResource: - return ReplaysResource(self._client) - @cached_property def with_raw_response(self) -> SessionsResourceWithRawResponse: """ @@ -361,10 +349,6 @@ def recording(self) -> AsyncRecordingResource: def uploads(self) -> AsyncUploadsResource: return AsyncUploadsResource(self._client) - @cached_property - def replays(self) -> AsyncReplaysResource: - return AsyncReplaysResource(self._client) - @cached_property def with_raw_response(self) -> AsyncSessionsResourceWithRawResponse: """ @@ -656,10 +640,6 @@ def recording(self) -> RecordingResourceWithRawResponse: def uploads(self) -> UploadsResourceWithRawResponse: return UploadsResourceWithRawResponse(self._sessions.uploads) - @cached_property - def replays(self) -> ReplaysResourceWithRawResponse: - return ReplaysResourceWithRawResponse(self._sessions.replays) - class AsyncSessionsResourceWithRawResponse: def __init__(self, sessions: AsyncSessionsResource) -> None: @@ -697,10 +677,6 @@ def recording(self) -> AsyncRecordingResourceWithRawResponse: def uploads(self) -> AsyncUploadsResourceWithRawResponse: return AsyncUploadsResourceWithRawResponse(self._sessions.uploads) - @cached_property - def replays(self) -> AsyncReplaysResourceWithRawResponse: - return AsyncReplaysResourceWithRawResponse(self._sessions.replays) - class SessionsResourceWithStreamingResponse: def __init__(self, sessions: SessionsResource) -> None: @@ -738,10 +714,6 @@ def recording(self) -> RecordingResourceWithStreamingResponse: def uploads(self) -> UploadsResourceWithStreamingResponse: return UploadsResourceWithStreamingResponse(self._sessions.uploads) - @cached_property - def replays(self) -> ReplaysResourceWithStreamingResponse: - return ReplaysResourceWithStreamingResponse(self._sessions.replays) - class AsyncSessionsResourceWithStreamingResponse: def __init__(self, sessions: AsyncSessionsResource) -> None: @@ -778,7 +750,3 @@ def recording(self) -> AsyncRecordingResourceWithStreamingResponse: @cached_property def uploads(self) -> AsyncUploadsResourceWithStreamingResponse: return AsyncUploadsResourceWithStreamingResponse(self._sessions.uploads) - - @cached_property - def replays(self) -> AsyncReplaysResourceWithStreamingResponse: - return AsyncReplaysResourceWithStreamingResponse(self._sessions.replays) diff --git a/src/browserbase/types/fetch_api_create_response.py b/src/browserbase/types/fetch_api_create_response.py index f97f5635..6a378000 100644 --- a/src/browserbase/types/fetch_api_create_response.py +++ b/src/browserbase/types/fetch_api_create_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict +from typing import Dict, Union from pydantic import Field as FieldInfo @@ -13,8 +13,12 @@ class FetchAPICreateResponse(BaseModel): id: str """Unique identifier for the fetch request""" - content: str - """The response body content""" + content: Union[str, Dict[str, object]] + """The response body content. + + A string for `raw` and `markdown` formats; a structured object for `json` format + (the schema-extracted result). + """ content_type: str = FieldInfo(alias="contentType") """The MIME type of the response""" diff --git a/src/browserbase/types/sessions/__init__.py b/src/browserbase/types/sessions/__init__.py index c7ea4671..0cef6b19 100644 --- a/src/browserbase/types/sessions/__init__.py +++ b/src/browserbase/types/sessions/__init__.py @@ -7,5 +7,4 @@ from .session_recording import SessionRecording as SessionRecording from .upload_create_params import UploadCreateParams as UploadCreateParams from .upload_create_response import UploadCreateResponse as UploadCreateResponse -from .replay_retrieve_response import ReplayRetrieveResponse as ReplayRetrieveResponse from .recording_retrieve_response import RecordingRetrieveResponse as RecordingRetrieveResponse diff --git a/src/browserbase/types/sessions/replay_retrieve_response.py b/src/browserbase/types/sessions/replay_retrieve_response.py deleted file mode 100644 index ec16398a..00000000 --- a/src/browserbase/types/sessions/replay_retrieve_response.py +++ /dev/null @@ -1,25 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["ReplayRetrieveResponse", "Page"] - - -class Page(BaseModel): - end_time_ms: int = FieldInfo(alias="endTimeMs") - - page_id: str = FieldInfo(alias="pageId") - - start_time_ms: int = FieldInfo(alias="startTimeMs") - - url: str - - -class ReplayRetrieveResponse(BaseModel): - page_count: int = FieldInfo(alias="pageCount") - - pages: List[Page] diff --git a/tests/api_resources/sessions/test_replays.py b/tests/api_resources/sessions/test_replays.py deleted file mode 100644 index a82c7880..00000000 --- a/tests/api_resources/sessions/test_replays.py +++ /dev/null @@ -1,242 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import httpx -import pytest -from respx import MockRouter - -from browserbase import Browserbase, AsyncBrowserbase -from tests.utils import assert_matches_type -from browserbase._response import ( - BinaryAPIResponse, - AsyncBinaryAPIResponse, - StreamedBinaryAPIResponse, - AsyncStreamedBinaryAPIResponse, -) -from browserbase.types.sessions import ReplayRetrieveResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestReplays: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_retrieve(self, client: Browserbase) -> None: - replay = client.sessions.replays.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) - - @parametrize - def test_raw_response_retrieve(self, client: Browserbase) -> None: - response = client.sessions.replays.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - replay = response.parse() - assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) - - @parametrize - def test_streaming_response_retrieve(self, client: Browserbase) -> None: - with client.sessions.replays.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - replay = response.parse() - assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_retrieve(self, client: Browserbase) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.sessions.replays.with_raw_response.retrieve( - "", - ) - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_retrieve_page(self, client: Browserbase, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - replay = client.sessions.replays.retrieve_page( - page_id="090", - id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert replay.is_closed - assert replay.json() == {"foo": "bar"} - assert cast(Any, replay.is_closed) is True - assert isinstance(replay, BinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_retrieve_page(self, client: Browserbase, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - - replay = client.sessions.replays.with_raw_response.retrieve_page( - page_id="090", - id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert replay.is_closed is True - assert replay.http_request.headers.get("X-Stainless-Lang") == "python" - assert replay.json() == {"foo": "bar"} - assert isinstance(replay, BinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_retrieve_page(self, client: Browserbase, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - with client.sessions.replays.with_streaming_response.retrieve_page( - page_id="090", - id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as replay: - assert not replay.is_closed - assert replay.http_request.headers.get("X-Stainless-Lang") == "python" - - assert replay.json() == {"foo": "bar"} - assert cast(Any, replay.is_closed) is True - assert isinstance(replay, StreamedBinaryAPIResponse) - - assert cast(Any, replay.is_closed) is True - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_path_params_retrieve_page(self, client: Browserbase) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.sessions.replays.with_raw_response.retrieve_page( - page_id="090", - id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `page_id` but received ''"): - client.sessions.replays.with_raw_response.retrieve_page( - page_id="", - id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - -class TestAsyncReplays: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @parametrize - async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: - replay = await async_client.sessions.replays.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) - - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.sessions.replays.with_raw_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - replay = await response.parse() - assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) - - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: - async with async_client.sessions.replays.with_streaming_response.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - replay = await response.parse() - assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncBrowserbase) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.sessions.replays.with_raw_response.retrieve( - "", - ) - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_retrieve_page(self, async_client: AsyncBrowserbase, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - replay = await async_client.sessions.replays.retrieve_page( - page_id="090", - id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert replay.is_closed - assert await replay.json() == {"foo": "bar"} - assert cast(Any, replay.is_closed) is True - assert isinstance(replay, AsyncBinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_retrieve_page(self, async_client: AsyncBrowserbase, respx_mock: MockRouter) -> None: - respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - - replay = await async_client.sessions.replays.with_raw_response.retrieve_page( - page_id="090", - id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - - assert replay.is_closed is True - assert replay.http_request.headers.get("X-Stainless-Lang") == "python" - assert await replay.json() == {"foo": "bar"} - assert isinstance(replay, AsyncBinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_retrieve_page( - self, async_client: AsyncBrowserbase, respx_mock: MockRouter - ) -> None: - respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( - return_value=httpx.Response(200, json={"foo": "bar"}) - ) - async with async_client.sessions.replays.with_streaming_response.retrieve_page( - page_id="090", - id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) as replay: - assert not replay.is_closed - assert replay.http_request.headers.get("X-Stainless-Lang") == "python" - - assert await replay.json() == {"foo": "bar"} - assert cast(Any, replay.is_closed) is True - assert isinstance(replay, AsyncStreamedBinaryAPIResponse) - - assert cast(Any, replay.is_closed) is True - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_path_params_retrieve_page(self, async_client: AsyncBrowserbase) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.sessions.replays.with_raw_response.retrieve_page( - page_id="090", - id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `page_id` but received ''"): - await async_client.sessions.replays.with_raw_response.retrieve_page( - page_id="", - id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) From de766051f87c90c307b93f0614b2df48fd4d4ee9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 20:54:49 +0000 Subject: [PATCH 2/7] feat: [AI-1972] - Move fetch v2 handler into /v1/fetch --- .stats.yml | 4 +-- src/browserbase/resources/fetch_api.py | 25 +++++++++++++++++++ .../types/fetch_api_create_params.py | 16 +++++++++++- tests/api_resources/test_fetch_api.py | 4 +++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 523861e0..85445503 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-1821faac6d1422fea15b3ba1f88c0f5f00c43464524e17d3fd1efd1ea148b7c4.yml -openapi_spec_hash: 1e3fba074314f557dc6973cf97ea6a69 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-3032c74460ad9c1c4b0a1b2d9107c556f292974a8ce12d525660a9cf31f10bc1.yml +openapi_spec_hash: 8ac1c673ce4e72b88bbbf30100b95e8f config_hash: cf04ecfb8dad5fbd8b85be25d6e9ec55 diff --git a/src/browserbase/resources/fetch_api.py b/src/browserbase/resources/fetch_api.py index dc016722..fc23ac33 100644 --- a/src/browserbase/resources/fetch_api.py +++ b/src/browserbase/resources/fetch_api.py @@ -2,6 +2,9 @@ from __future__ import annotations +from typing import Dict +from typing_extensions import Literal + import httpx from ..types import fetch_api_create_params @@ -47,7 +50,9 @@ def create( url: str, allow_insecure_ssl: bool | Omit = omit, allow_redirects: bool | Omit = omit, + format: Literal["raw", "json", "markdown"] | Omit = omit, proxies: bool | Omit = omit, + schema: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -65,8 +70,15 @@ def create( allow_redirects: Whether to follow HTTP redirects + format: Output format for the response content. `raw` (default) returns the response + body unchanged; `json` returns structured data (requires `schema`); `markdown` + returns the page as markdown. + proxies: Whether to enable proxy support for the request + schema: JSON Schema describing the desired structure of the response. Only used when + `format` is `json`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -82,7 +94,9 @@ def create( "url": url, "allow_insecure_ssl": allow_insecure_ssl, "allow_redirects": allow_redirects, + "format": format, "proxies": proxies, + "schema": schema, }, fetch_api_create_params.FetchAPICreateParams, ), @@ -119,7 +133,9 @@ async def create( url: str, allow_insecure_ssl: bool | Omit = omit, allow_redirects: bool | Omit = omit, + format: Literal["raw", "json", "markdown"] | Omit = omit, proxies: bool | Omit = omit, + schema: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -137,8 +153,15 @@ async def create( allow_redirects: Whether to follow HTTP redirects + format: Output format for the response content. `raw` (default) returns the response + body unchanged; `json` returns structured data (requires `schema`); `markdown` + returns the page as markdown. + proxies: Whether to enable proxy support for the request + schema: JSON Schema describing the desired structure of the response. Only used when + `format` is `json`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -154,7 +177,9 @@ async def create( "url": url, "allow_insecure_ssl": allow_insecure_ssl, "allow_redirects": allow_redirects, + "format": format, "proxies": proxies, + "schema": schema, }, fetch_api_create_params.FetchAPICreateParams, ), diff --git a/src/browserbase/types/fetch_api_create_params.py b/src/browserbase/types/fetch_api_create_params.py index 84a8a052..f83096f7 100644 --- a/src/browserbase/types/fetch_api_create_params.py +++ b/src/browserbase/types/fetch_api_create_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict +from typing import Dict +from typing_extensions import Literal, Required, Annotated, TypedDict from .._utils import PropertyInfo @@ -19,5 +20,18 @@ class FetchAPICreateParams(TypedDict, total=False): allow_redirects: Annotated[bool, PropertyInfo(alias="allowRedirects")] """Whether to follow HTTP redirects""" + format: Literal["raw", "json", "markdown"] + """Output format for the response content. + + `raw` (default) returns the response body unchanged; `json` returns structured + data (requires `schema`); `markdown` returns the page as markdown. + """ + proxies: bool """Whether to enable proxy support for the request""" + + schema: Dict[str, object] + """JSON Schema describing the desired structure of the response. + + Only used when `format` is `json`. + """ diff --git a/tests/api_resources/test_fetch_api.py b/tests/api_resources/test_fetch_api.py index b9a0455b..def3304d 100644 --- a/tests/api_resources/test_fetch_api.py +++ b/tests/api_resources/test_fetch_api.py @@ -30,7 +30,9 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: url="https://example.com", allow_insecure_ssl=True, allow_redirects=True, + format="raw", proxies=True, + schema={"foo": "bar"}, ) assert_matches_type(FetchAPICreateResponse, fetch_api, path=["response"]) @@ -77,7 +79,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas url="https://example.com", allow_insecure_ssl=True, allow_redirects=True, + format="raw", proxies=True, + schema={"foo": "bar"}, ) assert_matches_type(FetchAPICreateResponse, fetch_api, path=["response"]) From 9c0b78d40adf9e9e2f49a2dbb34e37aa622b8991 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 10:41:08 +0000 Subject: [PATCH 3/7] feat: [AI-1748][apps/api] Obtain custom certificates in API during session reservation --- .stats.yml | 4 +-- src/browserbase/resources/fetch_api.py | 25 ------------------- .../types/fetch_api_create_params.py | 16 +----------- .../types/fetch_api_create_response.py | 10 +++----- tests/api_resources/test_fetch_api.py | 4 --- 5 files changed, 6 insertions(+), 53 deletions(-) diff --git a/.stats.yml b/.stats.yml index 85445503..c2ae426f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-3032c74460ad9c1c4b0a1b2d9107c556f292974a8ce12d525660a9cf31f10bc1.yml -openapi_spec_hash: 8ac1c673ce4e72b88bbbf30100b95e8f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-090302cbdfdd8758ae5907f93842b16040a66772578d0327349135378e0bce40.yml +openapi_spec_hash: cb19458f0642bea3cf6eaf91113f9fda config_hash: cf04ecfb8dad5fbd8b85be25d6e9ec55 diff --git a/src/browserbase/resources/fetch_api.py b/src/browserbase/resources/fetch_api.py index fc23ac33..dc016722 100644 --- a/src/browserbase/resources/fetch_api.py +++ b/src/browserbase/resources/fetch_api.py @@ -2,9 +2,6 @@ from __future__ import annotations -from typing import Dict -from typing_extensions import Literal - import httpx from ..types import fetch_api_create_params @@ -50,9 +47,7 @@ def create( url: str, allow_insecure_ssl: bool | Omit = omit, allow_redirects: bool | Omit = omit, - format: Literal["raw", "json", "markdown"] | Omit = omit, proxies: bool | Omit = omit, - schema: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -70,15 +65,8 @@ def create( allow_redirects: Whether to follow HTTP redirects - format: Output format for the response content. `raw` (default) returns the response - body unchanged; `json` returns structured data (requires `schema`); `markdown` - returns the page as markdown. - proxies: Whether to enable proxy support for the request - schema: JSON Schema describing the desired structure of the response. Only used when - `format` is `json`. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -94,9 +82,7 @@ def create( "url": url, "allow_insecure_ssl": allow_insecure_ssl, "allow_redirects": allow_redirects, - "format": format, "proxies": proxies, - "schema": schema, }, fetch_api_create_params.FetchAPICreateParams, ), @@ -133,9 +119,7 @@ async def create( url: str, allow_insecure_ssl: bool | Omit = omit, allow_redirects: bool | Omit = omit, - format: Literal["raw", "json", "markdown"] | Omit = omit, proxies: bool | Omit = omit, - schema: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -153,15 +137,8 @@ async def create( allow_redirects: Whether to follow HTTP redirects - format: Output format for the response content. `raw` (default) returns the response - body unchanged; `json` returns structured data (requires `schema`); `markdown` - returns the page as markdown. - proxies: Whether to enable proxy support for the request - schema: JSON Schema describing the desired structure of the response. Only used when - `format` is `json`. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -177,9 +154,7 @@ async def create( "url": url, "allow_insecure_ssl": allow_insecure_ssl, "allow_redirects": allow_redirects, - "format": format, "proxies": proxies, - "schema": schema, }, fetch_api_create_params.FetchAPICreateParams, ), diff --git a/src/browserbase/types/fetch_api_create_params.py b/src/browserbase/types/fetch_api_create_params.py index f83096f7..84a8a052 100644 --- a/src/browserbase/types/fetch_api_create_params.py +++ b/src/browserbase/types/fetch_api_create_params.py @@ -2,8 +2,7 @@ from __future__ import annotations -from typing import Dict -from typing_extensions import Literal, Required, Annotated, TypedDict +from typing_extensions import Required, Annotated, TypedDict from .._utils import PropertyInfo @@ -20,18 +19,5 @@ class FetchAPICreateParams(TypedDict, total=False): allow_redirects: Annotated[bool, PropertyInfo(alias="allowRedirects")] """Whether to follow HTTP redirects""" - format: Literal["raw", "json", "markdown"] - """Output format for the response content. - - `raw` (default) returns the response body unchanged; `json` returns structured - data (requires `schema`); `markdown` returns the page as markdown. - """ - proxies: bool """Whether to enable proxy support for the request""" - - schema: Dict[str, object] - """JSON Schema describing the desired structure of the response. - - Only used when `format` is `json`. - """ diff --git a/src/browserbase/types/fetch_api_create_response.py b/src/browserbase/types/fetch_api_create_response.py index 6a378000..f97f5635 100644 --- a/src/browserbase/types/fetch_api_create_response.py +++ b/src/browserbase/types/fetch_api_create_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Union +from typing import Dict from pydantic import Field as FieldInfo @@ -13,12 +13,8 @@ class FetchAPICreateResponse(BaseModel): id: str """Unique identifier for the fetch request""" - content: Union[str, Dict[str, object]] - """The response body content. - - A string for `raw` and `markdown` formats; a structured object for `json` format - (the schema-extracted result). - """ + content: str + """The response body content""" content_type: str = FieldInfo(alias="contentType") """The MIME type of the response""" diff --git a/tests/api_resources/test_fetch_api.py b/tests/api_resources/test_fetch_api.py index def3304d..b9a0455b 100644 --- a/tests/api_resources/test_fetch_api.py +++ b/tests/api_resources/test_fetch_api.py @@ -30,9 +30,7 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: url="https://example.com", allow_insecure_ssl=True, allow_redirects=True, - format="raw", proxies=True, - schema={"foo": "bar"}, ) assert_matches_type(FetchAPICreateResponse, fetch_api, path=["response"]) @@ -79,9 +77,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas url="https://example.com", allow_insecure_ssl=True, allow_redirects=True, - format="raw", proxies=True, - schema={"foo": "bar"}, ) assert_matches_type(FetchAPICreateResponse, fetch_api, path=["response"]) From 23a3a2f0d156d438b0c9aa1430663dc51f49f11e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 20:35:30 +0000 Subject: [PATCH 4/7] feat(api): manual updates --- .stats.yml | 4 +-- src/browserbase/resources/fetch_api.py | 25 +++++++++++++++++++ .../types/fetch_api_create_params.py | 16 +++++++++++- .../types/fetch_api_create_response.py | 10 +++++--- tests/api_resources/test_fetch_api.py | 4 +++ 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/.stats.yml b/.stats.yml index c2ae426f..85445503 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-090302cbdfdd8758ae5907f93842b16040a66772578d0327349135378e0bce40.yml -openapi_spec_hash: cb19458f0642bea3cf6eaf91113f9fda +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-3032c74460ad9c1c4b0a1b2d9107c556f292974a8ce12d525660a9cf31f10bc1.yml +openapi_spec_hash: 8ac1c673ce4e72b88bbbf30100b95e8f config_hash: cf04ecfb8dad5fbd8b85be25d6e9ec55 diff --git a/src/browserbase/resources/fetch_api.py b/src/browserbase/resources/fetch_api.py index dc016722..fc23ac33 100644 --- a/src/browserbase/resources/fetch_api.py +++ b/src/browserbase/resources/fetch_api.py @@ -2,6 +2,9 @@ from __future__ import annotations +from typing import Dict +from typing_extensions import Literal + import httpx from ..types import fetch_api_create_params @@ -47,7 +50,9 @@ def create( url: str, allow_insecure_ssl: bool | Omit = omit, allow_redirects: bool | Omit = omit, + format: Literal["raw", "json", "markdown"] | Omit = omit, proxies: bool | Omit = omit, + schema: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -65,8 +70,15 @@ def create( allow_redirects: Whether to follow HTTP redirects + format: Output format for the response content. `raw` (default) returns the response + body unchanged; `json` returns structured data (requires `schema`); `markdown` + returns the page as markdown. + proxies: Whether to enable proxy support for the request + schema: JSON Schema describing the desired structure of the response. Only used when + `format` is `json`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -82,7 +94,9 @@ def create( "url": url, "allow_insecure_ssl": allow_insecure_ssl, "allow_redirects": allow_redirects, + "format": format, "proxies": proxies, + "schema": schema, }, fetch_api_create_params.FetchAPICreateParams, ), @@ -119,7 +133,9 @@ async def create( url: str, allow_insecure_ssl: bool | Omit = omit, allow_redirects: bool | Omit = omit, + format: Literal["raw", "json", "markdown"] | Omit = omit, proxies: bool | Omit = omit, + schema: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -137,8 +153,15 @@ async def create( allow_redirects: Whether to follow HTTP redirects + format: Output format for the response content. `raw` (default) returns the response + body unchanged; `json` returns structured data (requires `schema`); `markdown` + returns the page as markdown. + proxies: Whether to enable proxy support for the request + schema: JSON Schema describing the desired structure of the response. Only used when + `format` is `json`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -154,7 +177,9 @@ async def create( "url": url, "allow_insecure_ssl": allow_insecure_ssl, "allow_redirects": allow_redirects, + "format": format, "proxies": proxies, + "schema": schema, }, fetch_api_create_params.FetchAPICreateParams, ), diff --git a/src/browserbase/types/fetch_api_create_params.py b/src/browserbase/types/fetch_api_create_params.py index 84a8a052..f83096f7 100644 --- a/src/browserbase/types/fetch_api_create_params.py +++ b/src/browserbase/types/fetch_api_create_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict +from typing import Dict +from typing_extensions import Literal, Required, Annotated, TypedDict from .._utils import PropertyInfo @@ -19,5 +20,18 @@ class FetchAPICreateParams(TypedDict, total=False): allow_redirects: Annotated[bool, PropertyInfo(alias="allowRedirects")] """Whether to follow HTTP redirects""" + format: Literal["raw", "json", "markdown"] + """Output format for the response content. + + `raw` (default) returns the response body unchanged; `json` returns structured + data (requires `schema`); `markdown` returns the page as markdown. + """ + proxies: bool """Whether to enable proxy support for the request""" + + schema: Dict[str, object] + """JSON Schema describing the desired structure of the response. + + Only used when `format` is `json`. + """ diff --git a/src/browserbase/types/fetch_api_create_response.py b/src/browserbase/types/fetch_api_create_response.py index f97f5635..6a378000 100644 --- a/src/browserbase/types/fetch_api_create_response.py +++ b/src/browserbase/types/fetch_api_create_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict +from typing import Dict, Union from pydantic import Field as FieldInfo @@ -13,8 +13,12 @@ class FetchAPICreateResponse(BaseModel): id: str """Unique identifier for the fetch request""" - content: str - """The response body content""" + content: Union[str, Dict[str, object]] + """The response body content. + + A string for `raw` and `markdown` formats; a structured object for `json` format + (the schema-extracted result). + """ content_type: str = FieldInfo(alias="contentType") """The MIME type of the response""" diff --git a/tests/api_resources/test_fetch_api.py b/tests/api_resources/test_fetch_api.py index b9a0455b..def3304d 100644 --- a/tests/api_resources/test_fetch_api.py +++ b/tests/api_resources/test_fetch_api.py @@ -30,7 +30,9 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: url="https://example.com", allow_insecure_ssl=True, allow_redirects=True, + format="raw", proxies=True, + schema={"foo": "bar"}, ) assert_matches_type(FetchAPICreateResponse, fetch_api, path=["response"]) @@ -77,7 +79,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas url="https://example.com", allow_insecure_ssl=True, allow_redirects=True, + format="raw", proxies=True, + schema={"foo": "bar"}, ) assert_matches_type(FetchAPICreateResponse, fetch_api, path=["response"]) From 1d7beb84745bd33ae11c9948a4f057f8240467a0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 20:38:51 +0000 Subject: [PATCH 5/7] feat(api): manual updates --- .stats.yml | 6 +- api.md | 13 + .../resources/sessions/__init__.py | 14 + src/browserbase/resources/sessions/replays.py | 266 ++++++++++++++++++ .../resources/sessions/sessions.py | 32 +++ src/browserbase/types/sessions/__init__.py | 1 + .../sessions/replay_retrieve_response.py | 25 ++ tests/api_resources/sessions/test_replays.py | 242 ++++++++++++++++ 8 files changed, 596 insertions(+), 3 deletions(-) create mode 100644 src/browserbase/resources/sessions/replays.py create mode 100644 src/browserbase/types/sessions/replay_retrieve_response.py create mode 100644 tests/api_resources/sessions/test_replays.py diff --git a/.stats.yml b/.stats.yml index 85445503..724c9191 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-3032c74460ad9c1c4b0a1b2d9107c556f292974a8ce12d525660a9cf31f10bc1.yml +configured_endpoints: 23 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-17193ab4cc450d38ce1bfdb1a571001c04b754ba2ff2d2bb2dcf4a898d6c6c41.yml openapi_spec_hash: 8ac1c673ce4e72b88bbbf30100b95e8f -config_hash: cf04ecfb8dad5fbd8b85be25d6e9ec55 +config_hash: 40fbac80e24faaa0dc19e93368bcd821 diff --git a/api.md b/api.md index b6066cb8..581574a3 100644 --- a/api.md +++ b/api.md @@ -128,3 +128,16 @@ from browserbase.types.sessions import UploadCreateResponse Methods: - client.sessions.uploads.create(id, \*\*params) -> UploadCreateResponse + +## Replays + +Types: + +```python +from browserbase.types.sessions import ReplayRetrieveResponse +``` + +Methods: + +- client.sessions.replays.retrieve(id) -> ReplayRetrieveResponse +- client.sessions.replays.retrieve_page(page_id, \*, id) -> BinaryAPIResponse diff --git a/src/browserbase/resources/sessions/__init__.py b/src/browserbase/resources/sessions/__init__.py index b3877e12..e66ee0ce 100644 --- a/src/browserbase/resources/sessions/__init__.py +++ b/src/browserbase/resources/sessions/__init__.py @@ -8,6 +8,14 @@ LogsResourceWithStreamingResponse, AsyncLogsResourceWithStreamingResponse, ) +from .replays import ( + ReplaysResource, + AsyncReplaysResource, + ReplaysResourceWithRawResponse, + AsyncReplaysResourceWithRawResponse, + ReplaysResourceWithStreamingResponse, + AsyncReplaysResourceWithStreamingResponse, +) from .uploads import ( UploadsResource, AsyncUploadsResource, @@ -66,6 +74,12 @@ "AsyncUploadsResourceWithRawResponse", "UploadsResourceWithStreamingResponse", "AsyncUploadsResourceWithStreamingResponse", + "ReplaysResource", + "AsyncReplaysResource", + "ReplaysResourceWithRawResponse", + "AsyncReplaysResourceWithRawResponse", + "ReplaysResourceWithStreamingResponse", + "AsyncReplaysResourceWithStreamingResponse", "SessionsResource", "AsyncSessionsResource", "SessionsResourceWithRawResponse", diff --git a/src/browserbase/resources/sessions/replays.py b/src/browserbase/resources/sessions/replays.py new file mode 100644 index 00000000..c9240356 --- /dev/null +++ b/src/browserbase/resources/sessions/replays.py @@ -0,0 +1,266 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import path_template +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + BinaryAPIResponse, + AsyncBinaryAPIResponse, + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + to_custom_raw_response_wrapper, + async_to_streamed_response_wrapper, + to_custom_streamed_response_wrapper, + async_to_custom_raw_response_wrapper, + async_to_custom_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.sessions.replay_retrieve_response import ReplayRetrieveResponse + +__all__ = ["ReplaysResource", "AsyncReplaysResource"] + + +class ReplaysResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ReplaysResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/browserbase/sdk-python#accessing-raw-response-data-eg-headers + """ + return ReplaysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ReplaysResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/browserbase/sdk-python#with_streaming_response + """ + return ReplaysResourceWithStreamingResponse(self) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ReplayRetrieveResponse: + """ + Returns page metadata for a session replay, including timing information and the + URL of each page's HLS playlist. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/v1/sessions/{id}/replays", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ReplayRetrieveResponse, + ) + + def retrieve_page( + self, + page_id: str, + *, + id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> BinaryAPIResponse: + """ + Returns an HLS VOD media playlist (.m3u8) for a specific page of a session + replay. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not page_id: + raise ValueError(f"Expected a non-empty value for `page_id` but received {page_id!r}") + extra_headers = {"Accept": "application/vnd.apple.mpegurl", **(extra_headers or {})} + return self._get( + path_template("/v1/sessions/{id}/replays/{page_id}", id=id, page_id=page_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BinaryAPIResponse, + ) + + +class AsyncReplaysResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncReplaysResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/browserbase/sdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncReplaysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncReplaysResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/browserbase/sdk-python#with_streaming_response + """ + return AsyncReplaysResourceWithStreamingResponse(self) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ReplayRetrieveResponse: + """ + Returns page metadata for a session replay, including timing information and the + URL of each page's HLS playlist. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/v1/sessions/{id}/replays", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ReplayRetrieveResponse, + ) + + async def retrieve_page( + self, + page_id: str, + *, + id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncBinaryAPIResponse: + """ + Returns an HLS VOD media playlist (.m3u8) for a specific page of a session + replay. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not page_id: + raise ValueError(f"Expected a non-empty value for `page_id` but received {page_id!r}") + extra_headers = {"Accept": "application/vnd.apple.mpegurl", **(extra_headers or {})} + return await self._get( + path_template("/v1/sessions/{id}/replays/{page_id}", id=id, page_id=page_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AsyncBinaryAPIResponse, + ) + + +class ReplaysResourceWithRawResponse: + def __init__(self, replays: ReplaysResource) -> None: + self._replays = replays + + self.retrieve = to_raw_response_wrapper( + replays.retrieve, + ) + self.retrieve_page = to_custom_raw_response_wrapper( + replays.retrieve_page, + BinaryAPIResponse, + ) + + +class AsyncReplaysResourceWithRawResponse: + def __init__(self, replays: AsyncReplaysResource) -> None: + self._replays = replays + + self.retrieve = async_to_raw_response_wrapper( + replays.retrieve, + ) + self.retrieve_page = async_to_custom_raw_response_wrapper( + replays.retrieve_page, + AsyncBinaryAPIResponse, + ) + + +class ReplaysResourceWithStreamingResponse: + def __init__(self, replays: ReplaysResource) -> None: + self._replays = replays + + self.retrieve = to_streamed_response_wrapper( + replays.retrieve, + ) + self.retrieve_page = to_custom_streamed_response_wrapper( + replays.retrieve_page, + StreamedBinaryAPIResponse, + ) + + +class AsyncReplaysResourceWithStreamingResponse: + def __init__(self, replays: AsyncReplaysResource) -> None: + self._replays = replays + + self.retrieve = async_to_streamed_response_wrapper( + replays.retrieve, + ) + self.retrieve_page = async_to_custom_streamed_response_wrapper( + replays.retrieve_page, + AsyncStreamedBinaryAPIResponse, + ) diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index ce3de98f..a54d7a72 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -16,6 +16,14 @@ AsyncLogsResourceWithStreamingResponse, ) from ...types import session_list_params, session_create_params, session_update_params +from .replays import ( + ReplaysResource, + AsyncReplaysResource, + ReplaysResourceWithRawResponse, + AsyncReplaysResourceWithRawResponse, + ReplaysResourceWithStreamingResponse, + AsyncReplaysResourceWithStreamingResponse, +) from .uploads import ( UploadsResource, AsyncUploadsResource, @@ -77,6 +85,10 @@ def recording(self) -> RecordingResource: def uploads(self) -> UploadsResource: return UploadsResource(self._client) + @cached_property + def replays(self) -> ReplaysResource: + return ReplaysResource(self._client) + @cached_property def with_raw_response(self) -> SessionsResourceWithRawResponse: """ @@ -349,6 +361,10 @@ def recording(self) -> AsyncRecordingResource: def uploads(self) -> AsyncUploadsResource: return AsyncUploadsResource(self._client) + @cached_property + def replays(self) -> AsyncReplaysResource: + return AsyncReplaysResource(self._client) + @cached_property def with_raw_response(self) -> AsyncSessionsResourceWithRawResponse: """ @@ -640,6 +656,10 @@ def recording(self) -> RecordingResourceWithRawResponse: def uploads(self) -> UploadsResourceWithRawResponse: return UploadsResourceWithRawResponse(self._sessions.uploads) + @cached_property + def replays(self) -> ReplaysResourceWithRawResponse: + return ReplaysResourceWithRawResponse(self._sessions.replays) + class AsyncSessionsResourceWithRawResponse: def __init__(self, sessions: AsyncSessionsResource) -> None: @@ -677,6 +697,10 @@ def recording(self) -> AsyncRecordingResourceWithRawResponse: def uploads(self) -> AsyncUploadsResourceWithRawResponse: return AsyncUploadsResourceWithRawResponse(self._sessions.uploads) + @cached_property + def replays(self) -> AsyncReplaysResourceWithRawResponse: + return AsyncReplaysResourceWithRawResponse(self._sessions.replays) + class SessionsResourceWithStreamingResponse: def __init__(self, sessions: SessionsResource) -> None: @@ -714,6 +738,10 @@ def recording(self) -> RecordingResourceWithStreamingResponse: def uploads(self) -> UploadsResourceWithStreamingResponse: return UploadsResourceWithStreamingResponse(self._sessions.uploads) + @cached_property + def replays(self) -> ReplaysResourceWithStreamingResponse: + return ReplaysResourceWithStreamingResponse(self._sessions.replays) + class AsyncSessionsResourceWithStreamingResponse: def __init__(self, sessions: AsyncSessionsResource) -> None: @@ -750,3 +778,7 @@ def recording(self) -> AsyncRecordingResourceWithStreamingResponse: @cached_property def uploads(self) -> AsyncUploadsResourceWithStreamingResponse: return AsyncUploadsResourceWithStreamingResponse(self._sessions.uploads) + + @cached_property + def replays(self) -> AsyncReplaysResourceWithStreamingResponse: + return AsyncReplaysResourceWithStreamingResponse(self._sessions.replays) diff --git a/src/browserbase/types/sessions/__init__.py b/src/browserbase/types/sessions/__init__.py index 0cef6b19..c7ea4671 100644 --- a/src/browserbase/types/sessions/__init__.py +++ b/src/browserbase/types/sessions/__init__.py @@ -7,4 +7,5 @@ from .session_recording import SessionRecording as SessionRecording from .upload_create_params import UploadCreateParams as UploadCreateParams from .upload_create_response import UploadCreateResponse as UploadCreateResponse +from .replay_retrieve_response import ReplayRetrieveResponse as ReplayRetrieveResponse from .recording_retrieve_response import RecordingRetrieveResponse as RecordingRetrieveResponse diff --git a/src/browserbase/types/sessions/replay_retrieve_response.py b/src/browserbase/types/sessions/replay_retrieve_response.py new file mode 100644 index 00000000..ec16398a --- /dev/null +++ b/src/browserbase/types/sessions/replay_retrieve_response.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["ReplayRetrieveResponse", "Page"] + + +class Page(BaseModel): + end_time_ms: int = FieldInfo(alias="endTimeMs") + + page_id: str = FieldInfo(alias="pageId") + + start_time_ms: int = FieldInfo(alias="startTimeMs") + + url: str + + +class ReplayRetrieveResponse(BaseModel): + page_count: int = FieldInfo(alias="pageCount") + + pages: List[Page] diff --git a/tests/api_resources/sessions/test_replays.py b/tests/api_resources/sessions/test_replays.py new file mode 100644 index 00000000..a82c7880 --- /dev/null +++ b/tests/api_resources/sessions/test_replays.py @@ -0,0 +1,242 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import httpx +import pytest +from respx import MockRouter + +from browserbase import Browserbase, AsyncBrowserbase +from tests.utils import assert_matches_type +from browserbase._response import ( + BinaryAPIResponse, + AsyncBinaryAPIResponse, + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, +) +from browserbase.types.sessions import ReplayRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestReplays: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Browserbase) -> None: + replay = client.sessions.replays.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Browserbase) -> None: + response = client.sessions.replays.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + replay = response.parse() + assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Browserbase) -> None: + with client.sessions.replays.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + replay = response.parse() + assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Browserbase) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.sessions.replays.with_raw_response.retrieve( + "", + ) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_retrieve_page(self, client: Browserbase, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + replay = client.sessions.replays.retrieve_page( + page_id="090", + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert replay.is_closed + assert replay.json() == {"foo": "bar"} + assert cast(Any, replay.is_closed) is True + assert isinstance(replay, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_retrieve_page(self, client: Browserbase, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + replay = client.sessions.replays.with_raw_response.retrieve_page( + page_id="090", + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert replay.is_closed is True + assert replay.http_request.headers.get("X-Stainless-Lang") == "python" + assert replay.json() == {"foo": "bar"} + assert isinstance(replay, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_retrieve_page(self, client: Browserbase, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + with client.sessions.replays.with_streaming_response.retrieve_page( + page_id="090", + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as replay: + assert not replay.is_closed + assert replay.http_request.headers.get("X-Stainless-Lang") == "python" + + assert replay.json() == {"foo": "bar"} + assert cast(Any, replay.is_closed) is True + assert isinstance(replay, StreamedBinaryAPIResponse) + + assert cast(Any, replay.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_retrieve_page(self, client: Browserbase) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.sessions.replays.with_raw_response.retrieve_page( + page_id="090", + id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `page_id` but received ''"): + client.sessions.replays.with_raw_response.retrieve_page( + page_id="", + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncReplays: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: + replay = await async_client.sessions.replays.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: + response = await async_client.sessions.replays.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + replay = await response.parse() + assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: + async with async_client.sessions.replays.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + replay = await response.parse() + assert_matches_type(ReplayRetrieveResponse, replay, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncBrowserbase) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.sessions.replays.with_raw_response.retrieve( + "", + ) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_retrieve_page(self, async_client: AsyncBrowserbase, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + replay = await async_client.sessions.replays.retrieve_page( + page_id="090", + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert replay.is_closed + assert await replay.json() == {"foo": "bar"} + assert cast(Any, replay.is_closed) is True + assert isinstance(replay, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_retrieve_page(self, async_client: AsyncBrowserbase, respx_mock: MockRouter) -> None: + respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + replay = await async_client.sessions.replays.with_raw_response.retrieve_page( + page_id="090", + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert replay.is_closed is True + assert replay.http_request.headers.get("X-Stainless-Lang") == "python" + assert await replay.json() == {"foo": "bar"} + assert isinstance(replay, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_retrieve_page( + self, async_client: AsyncBrowserbase, respx_mock: MockRouter + ) -> None: + respx_mock.get("/v1/sessions/182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e/replays/090").mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + async with async_client.sessions.replays.with_streaming_response.retrieve_page( + page_id="090", + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as replay: + assert not replay.is_closed + assert replay.http_request.headers.get("X-Stainless-Lang") == "python" + + assert await replay.json() == {"foo": "bar"} + assert cast(Any, replay.is_closed) is True + assert isinstance(replay, AsyncStreamedBinaryAPIResponse) + + assert cast(Any, replay.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_retrieve_page(self, async_client: AsyncBrowserbase) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.sessions.replays.with_raw_response.retrieve_page( + page_id="090", + id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `page_id` but received ''"): + await async_client.sessions.replays.with_raw_response.retrieve_page( + page_id="", + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) From 61dc3a34dbcfe3df54cdf823eb667fe4b19ec436 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 21:33:17 +0000 Subject: [PATCH 6/7] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 724c9191..f40d1167 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 23 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-17193ab4cc450d38ce1bfdb1a571001c04b754ba2ff2d2bb2dcf4a898d6c6c41.yml -openapi_spec_hash: 8ac1c673ce4e72b88bbbf30100b95e8f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-b2831c9c836f039762834825afdc20569587a825d29ac5c3748c78b009bf059b.yml +openapi_spec_hash: dd85a934900cb6583f12ebf6117be884 config_hash: 40fbac80e24faaa0dc19e93368bcd821 From 24f6a9d651cd4b2d4960d1126141a242386624e9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 21:33:39 +0000 Subject: [PATCH 7/7] release: 1.11.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ pyproject.toml | 2 +- src/browserbase/_version.py | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index eb4e0dba..caf14871 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.10.0" + ".": "1.11.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ac4cadc0..859bfcce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 1.11.0 (2026-05-20) + +Full Changelog: [v1.10.0...v1.11.0](https://github.com/browserbase/sdk-python/compare/v1.10.0...v1.11.0) + +### Features + +* [AI-1748][apps/api] Obtain custom certificates in API during session reservation ([9c0b78d](https://github.com/browserbase/sdk-python/commit/9c0b78d40adf9e9e2f49a2dbb34e37aa622b8991)) +* [AI-1972] - Move fetch v2 handler into /v1/fetch ([de76605](https://github.com/browserbase/sdk-python/commit/de766051f87c90c307b93f0614b2df48fd4d4ee9)) +* [AI-1993] - Surface structured JSON content on /v2/fetch ([97f1c02](https://github.com/browserbase/sdk-python/commit/97f1c02a520d433abe467b11a04f7fbab85fd3f3)) +* **api:** manual updates ([1d7beb8](https://github.com/browserbase/sdk-python/commit/1d7beb84745bd33ae11c9948a4f057f8240467a0)) +* **api:** manual updates ([23a3a2f](https://github.com/browserbase/sdk-python/commit/23a3a2f0d156d438b0c9aa1430663dc51f49f11e)) + ## 1.10.0 (2026-05-13) Full Changelog: [v1.9.0...v1.10.0](https://github.com/browserbase/sdk-python/compare/v1.9.0...v1.10.0) diff --git a/pyproject.toml b/pyproject.toml index 404289b8..3338ab43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "browserbase" -version = "1.10.0" +version = "1.11.0" description = "The official Python library for the Browserbase API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/browserbase/_version.py b/src/browserbase/_version.py index c811ccc9..1184ad60 100644 --- a/src/browserbase/_version.py +++ b/src/browserbase/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "browserbase" -__version__ = "1.10.0" # x-release-please-version +__version__ = "1.11.0" # x-release-please-version