diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b57e9e3..fa1b60cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: lint: timeout-minutes: 10 name: lint - runs-on: ${{ github.repository == 'stainless-sdks/browserbase-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -44,7 +44,7 @@ jobs: permissions: contents: read id-token: write - runs-on: ${{ github.repository == 'stainless-sdks/browserbase-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -84,7 +84,7 @@ jobs: test: timeout-minutes: 10 name: test - runs-on: ${{ github.repository == 'stainless-sdks/browserbase-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7ccfe12c..bc845f32 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.15.0" + ".": "1.16.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 1ac83c1a..5aa6e87e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 38 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-98cc68ad9afa71355baf2b31f305a5e2f3a315fd311c96659405eff96b8f2b1e.yml -openapi_spec_hash: 71dfbc1021a33dd7fc9d82844965b1b3 -config_hash: 6209a285dd5980f5c418fe6575723aef +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/browserbase-0764e25c8c07b520e25c38c8455fca106a51fd8b13f8260b9db9729d6a556b2e.yml +openapi_spec_hash: aed91a76db495a87ca16737b91b0c51c +config_hash: 12a5eb5ac818623045ae77075ee3dd53 diff --git a/CHANGELOG.md b/CHANGELOG.md index 76e15c3c..6b099255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 1.16.0 (2026-08-10) + +Full Changelog: [v1.15.0...v1.16.0](https://github.com/browserbase/sdk-python/compare/v1.15.0...v1.16.0) + +### Features + +* [AI-2679] Deprecate presigned-url context uploads ([72cc359](https://github.com/browserbase/sdk-python/commit/72cc35934740f8ccb84f036bba455051177e80b5)) +* [CORE-2365] Add optional name to create and get Context API ([b2f4057](https://github.com/browserbase/sdk-python/commit/b2f4057417297ea0f6626e72b51d8a6c435960f6)) +* [STG-2717] Pass-thru `proxy` config for session creation in `/v1/agents/runs` ([37ac241](https://github.com/browserbase/sdk-python/commit/37ac24172e12a3a6417714d12afa7984217dde06)) +* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([c9098bb](https://github.com/browserbase/sdk-python/commit/c9098bb75d7ad2a4a84e55c0f0b0c65a5a61078b)) + ## 1.15.0 (2026-07-14) Full Changelog: [v1.14.0...v1.15.0](https://github.com/browserbase/sdk-python/compare/v1.14.0...v1.15.0) diff --git a/pyproject.toml b/pyproject.toml index 86985708..1d429b57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "browserbase" -version = "1.15.0" +version = "1.16.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 cb52e162..797996d4 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.15.0" # x-release-please-version +__version__ = "1.16.0" # x-release-please-version diff --git a/src/browserbase/resources/contexts.py b/src/browserbase/resources/contexts.py index f3f8f650..af39d96d 100644 --- a/src/browserbase/resources/contexts.py +++ b/src/browserbase/resources/contexts.py @@ -2,6 +2,8 @@ from __future__ import annotations +import typing_extensions + import httpx from ..types import context_create_params @@ -46,6 +48,7 @@ def with_streaming_response(self) -> ContextsResourceWithStreamingResponse: def create( self, *, + name: str | Omit = omit, project_id: str | 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. @@ -57,9 +60,13 @@ def create( """Create a Context Args: - project_id: The Project ID. + name: Optional user-defined name for the Context. + + Leading and trailing whitespace is + trimmed before storage. Names are unique within the project among active + Contexts, compared case-insensitively. - Can be found in + project_id: The Project ID. Can be found in [Settings](https://www.browserbase.com/settings). Optional - if not provided, the project will be inferred from the API key. @@ -73,7 +80,13 @@ def create( """ return self._post( "/v1/contexts", - body=maybe_transform({"project_id": project_id}, context_create_params.ContextCreateParams), + body=maybe_transform( + { + "name": name, + "project_id": project_id, + }, + context_create_params.ContextCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -113,6 +126,7 @@ def retrieve( cast_to=Context, ) + @typing_extensions.deprecated("deprecated") def update( self, id: str, @@ -204,6 +218,7 @@ def with_streaming_response(self) -> AsyncContextsResourceWithStreamingResponse: async def create( self, *, + name: str | Omit = omit, project_id: str | 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. @@ -215,9 +230,13 @@ async def create( """Create a Context Args: - project_id: The Project ID. + name: Optional user-defined name for the Context. - Can be found in + Leading and trailing whitespace is + trimmed before storage. Names are unique within the project among active + Contexts, compared case-insensitively. + + project_id: The Project ID. Can be found in [Settings](https://www.browserbase.com/settings). Optional - if not provided, the project will be inferred from the API key. @@ -231,7 +250,13 @@ async def create( """ return await self._post( "/v1/contexts", - body=await async_maybe_transform({"project_id": project_id}, context_create_params.ContextCreateParams), + body=await async_maybe_transform( + { + "name": name, + "project_id": project_id, + }, + context_create_params.ContextCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -271,6 +296,7 @@ async def retrieve( cast_to=Context, ) + @typing_extensions.deprecated("deprecated") async def update( self, id: str, @@ -349,8 +375,10 @@ def __init__(self, contexts: ContextsResource) -> None: self.retrieve = to_raw_response_wrapper( contexts.retrieve, ) - self.update = to_raw_response_wrapper( - contexts.update, + self.update = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + contexts.update, # pyright: ignore[reportDeprecated], + ) ) self.delete = to_raw_response_wrapper( contexts.delete, @@ -367,8 +395,10 @@ def __init__(self, contexts: AsyncContextsResource) -> None: self.retrieve = async_to_raw_response_wrapper( contexts.retrieve, ) - self.update = async_to_raw_response_wrapper( - contexts.update, + self.update = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + contexts.update, # pyright: ignore[reportDeprecated], + ) ) self.delete = async_to_raw_response_wrapper( contexts.delete, @@ -385,8 +415,10 @@ def __init__(self, contexts: ContextsResource) -> None: self.retrieve = to_streamed_response_wrapper( contexts.retrieve, ) - self.update = to_streamed_response_wrapper( - contexts.update, + self.update = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + contexts.update, # pyright: ignore[reportDeprecated], + ) ) self.delete = to_streamed_response_wrapper( contexts.delete, @@ -403,8 +435,10 @@ def __init__(self, contexts: AsyncContextsResource) -> None: self.retrieve = async_to_streamed_response_wrapper( contexts.retrieve, ) - self.update = async_to_streamed_response_wrapper( - contexts.update, + self.update = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + contexts.update, # pyright: ignore[reportDeprecated], + ) ) self.delete = async_to_streamed_response_wrapper( contexts.delete, diff --git a/src/browserbase/types/agents/run_create_params.py b/src/browserbase/types/agents/run_create_params.py index 00a45a42..f736c8b6 100644 --- a/src/browserbase/types/agents/run_create_params.py +++ b/src/browserbase/types/agents/run_create_params.py @@ -2,12 +2,22 @@ from __future__ import annotations -from typing import Dict -from typing_extensions import Required, Annotated, TypedDict +from typing import Dict, Union, Iterable +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict from ..._utils import PropertyInfo -__all__ = ["RunCreateParams", "BrowserSettings", "BrowserSettingsContext", "Variables"] +__all__ = [ + "RunCreateParams", + "BrowserSettings", + "BrowserSettingsContext", + "BrowserSettingsProxiesUnionMember0", + "BrowserSettingsProxiesUnionMember0BrowserbaseProxyConfig", + "BrowserSettingsProxiesUnionMember0BrowserbaseProxyConfigGeolocation", + "BrowserSettingsProxiesUnionMember0ExternalProxyConfig", + "BrowserSettingsProxiesUnionMember0NoneProxyConfig", + "Variables", +] class RunCreateParams(TypedDict, total=False): @@ -52,6 +62,74 @@ class BrowserSettingsContext(TypedDict, total=False): """Whether to persist the context after browsing. Defaults to false.""" +class BrowserSettingsProxiesUnionMember0BrowserbaseProxyConfigGeolocation(TypedDict, total=False): + """Geographic location for the proxy. Optional.""" + + country: Required[str] + """Country code in ISO 3166-1 alpha-2 format""" + + city: str + """Name of the city. Use spaces for multi-word city names. Optional.""" + + state: str + """US state code (2 characters). Must also specify US as the country. Optional.""" + + +class BrowserSettingsProxiesUnionMember0BrowserbaseProxyConfig(TypedDict, total=False): + type: Required[Literal["browserbase"]] + """Type of proxy. + + Always use 'browserbase' for the Browserbase managed proxy network. + """ + + domain_pattern: Annotated[str, PropertyInfo(alias="domainPattern")] + """Domain pattern for which this proxy should be used. + + If omitted, defaults to all domains. Optional. + """ + + geolocation: BrowserSettingsProxiesUnionMember0BrowserbaseProxyConfigGeolocation + """Geographic location for the proxy. Optional.""" + + +class BrowserSettingsProxiesUnionMember0ExternalProxyConfig(TypedDict, total=False): + server: Required[str] + """Server URL for external proxy. Required.""" + + type: Required[Literal["external"]] + """Type of proxy. Always 'external' for this config.""" + + domain_pattern: Annotated[str, PropertyInfo(alias="domainPattern")] + """Domain pattern for which this proxy should be used. + + If omitted, defaults to all domains. Optional. + """ + + password: str + """Password for external proxy authentication. Optional.""" + + username: str + """Username for external proxy authentication. Optional.""" + + +class BrowserSettingsProxiesUnionMember0NoneProxyConfig(TypedDict, total=False): + type: Required[Literal["none"]] + """Type of proxy. Always 'none' for this config.""" + + domain_pattern: Annotated[str, PropertyInfo(alias="domainPattern")] + """Domain pattern for which this proxy should be used. + + If omitted, defaults to all domains. Optional. + """ + + +BrowserSettingsProxiesUnionMember0: TypeAlias = Union[ + BrowserSettingsProxiesUnionMember0BrowserbaseProxyConfig, + BrowserSettingsProxiesUnionMember0ExternalProxyConfig, + BrowserSettingsProxiesUnionMember0NoneProxyConfig, +] + + class BrowserSettings(TypedDict, total=False): """Browser configuration for the agent's session. @@ -60,8 +138,11 @@ class BrowserSettings(TypedDict, total=False): context: BrowserSettingsContext - proxies: bool - """Set true to route the agent's browser session through the default proxy.""" + proxies: Union[Iterable[BrowserSettingsProxiesUnionMember0], bool] + """Proxy configuration. + + Can be true for default proxy, or an array of proxy configurations. + """ verified: bool """Set true to enable Browserbase Verified for the session.""" diff --git a/src/browserbase/types/context.py b/src/browserbase/types/context.py index cb5c32fd..8e839c2d 100644 --- a/src/browserbase/types/context.py +++ b/src/browserbase/types/context.py @@ -1,5 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Optional from datetime import datetime from pydantic import Field as FieldInfo @@ -18,3 +19,10 @@ class Context(BaseModel): """The Project ID linked to the uploaded Context.""" updated_at: datetime = FieldInfo(alias="updatedAt") + + name: Optional[str] = None + """Optional user-defined name for the Context. + + Leading and trailing whitespace is trimmed before storage. Names are unique + within the project among active Contexts, compared case-insensitively. + """ diff --git a/src/browserbase/types/context_create_params.py b/src/browserbase/types/context_create_params.py index 7f422f2d..5fffe71a 100644 --- a/src/browserbase/types/context_create_params.py +++ b/src/browserbase/types/context_create_params.py @@ -10,6 +10,13 @@ class ContextCreateParams(TypedDict, total=False): + name: str + """Optional user-defined name for the Context. + + Leading and trailing whitespace is trimmed before storage. Names are unique + within the project among active Contexts, compared case-insensitively. + """ + project_id: Annotated[str, PropertyInfo(alias="projectId")] """The Project ID. diff --git a/src/browserbase/types/context_create_response.py b/src/browserbase/types/context_create_response.py index 8e2f7aa3..34090664 100644 --- a/src/browserbase/types/context_create_response.py +++ b/src/browserbase/types/context_create_response.py @@ -26,4 +26,9 @@ class ContextCreateResponse(BaseModel): """The public key to encrypt the user-data-directory.""" upload_url: str = FieldInfo(alias="uploadUrl") - """An upload URL to upload a custom user-data-directory.""" + """Deprecated. + + Browserbase no longer supports context uploads via the API; this field always + contains a non-functional sentinel URL and remains only for backwards + compatibility. + """ diff --git a/src/browserbase/types/context_update_response.py b/src/browserbase/types/context_update_response.py index 7e16c624..69dc9c0f 100644 --- a/src/browserbase/types/context_update_response.py +++ b/src/browserbase/types/context_update_response.py @@ -26,4 +26,9 @@ class ContextUpdateResponse(BaseModel): """The public key to encrypt the user-data-directory.""" upload_url: str = FieldInfo(alias="uploadUrl") - """An upload URL to upload a custom user-data-directory.""" + """Deprecated. + + Browserbase no longer supports context uploads via the API; this field always + contains a non-functional sentinel URL and remains only for backwards + compatibility. + """ diff --git a/tests/api_resources/agents/test_runs.py b/tests/api_resources/agents/test_runs.py index b4912030..769039bd 100644 --- a/tests/api_resources/agents/test_runs.py +++ b/tests/api_resources/agents/test_runs.py @@ -40,7 +40,17 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: "id": "id", "persist": True, }, - "proxies": True, + "proxies": [ + { + "type": "browserbase", + "domain_pattern": "domainPattern", + "geolocation": { + "country": "xx", + "city": "city", + "state": "xx", + }, + } + ], "verified": True, }, result_schema={"foo": "bar"}, @@ -223,7 +233,17 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas "id": "id", "persist": True, }, - "proxies": True, + "proxies": [ + { + "type": "browserbase", + "domain_pattern": "domainPattern", + "geolocation": { + "country": "xx", + "city": "city", + "state": "xx", + }, + } + ], "verified": True, }, result_schema={"foo": "bar"}, diff --git a/tests/api_resources/test_contexts.py b/tests/api_resources/test_contexts.py index 31fb97d0..3ebcf672 100644 --- a/tests/api_resources/test_contexts.py +++ b/tests/api_resources/test_contexts.py @@ -11,6 +11,8 @@ from tests.utils import assert_matches_type from browserbase.types import Context, ContextCreateResponse, ContextUpdateResponse +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -25,6 +27,7 @@ def test_method_create(self, client: Browserbase) -> None: @parametrize def test_method_create_with_all_params(self, client: Browserbase) -> None: context = client.contexts.create( + name="x", project_id="projectId", ) assert_matches_type(ContextCreateResponse, context, path=["response"]) @@ -89,16 +92,19 @@ def test_path_params_retrieve(self, client: Browserbase) -> None: @parametrize def test_method_update(self, client: Browserbase) -> None: - context = client.contexts.update( - "id", - ) + with pytest.warns(DeprecationWarning): + context = client.contexts.update( + "id", + ) + assert_matches_type(ContextUpdateResponse, context, path=["response"]) @parametrize def test_raw_response_update(self, client: Browserbase) -> None: - response = client.contexts.with_raw_response.update( - "id", - ) + with pytest.warns(DeprecationWarning): + response = client.contexts.with_raw_response.update( + "id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -107,23 +113,25 @@ def test_raw_response_update(self, client: Browserbase) -> None: @parametrize def test_streaming_response_update(self, client: Browserbase) -> None: - with client.contexts.with_streaming_response.update( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.contexts.with_streaming_response.update( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - context = response.parse() - assert_matches_type(ContextUpdateResponse, context, path=["response"]) + context = response.parse() + assert_matches_type(ContextUpdateResponse, context, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_path_params_update(self, client: Browserbase) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.contexts.with_raw_response.update( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.contexts.with_raw_response.update( + "", + ) @parametrize def test_method_delete(self, client: Browserbase) -> None: @@ -177,6 +185,7 @@ async def test_method_create(self, async_client: AsyncBrowserbase) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncBrowserbase) -> None: context = await async_client.contexts.create( + name="x", project_id="projectId", ) assert_matches_type(ContextCreateResponse, context, path=["response"]) @@ -241,16 +250,19 @@ async def test_path_params_retrieve(self, async_client: AsyncBrowserbase) -> Non @parametrize async def test_method_update(self, async_client: AsyncBrowserbase) -> None: - context = await async_client.contexts.update( - "id", - ) + with pytest.warns(DeprecationWarning): + context = await async_client.contexts.update( + "id", + ) + assert_matches_type(ContextUpdateResponse, context, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.contexts.with_raw_response.update( - "id", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.contexts.with_raw_response.update( + "id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -259,23 +271,25 @@ async def test_raw_response_update(self, async_client: AsyncBrowserbase) -> None @parametrize async def test_streaming_response_update(self, async_client: AsyncBrowserbase) -> None: - async with async_client.contexts.with_streaming_response.update( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.contexts.with_streaming_response.update( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - context = await response.parse() - assert_matches_type(ContextUpdateResponse, context, path=["response"]) + context = await response.parse() + assert_matches_type(ContextUpdateResponse, context, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_path_params_update(self, async_client: AsyncBrowserbase) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.contexts.with_raw_response.update( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.contexts.with_raw_response.update( + "", + ) @parametrize async def test_method_delete(self, async_client: AsyncBrowserbase) -> None: