diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 124b9841..486a203f 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.119.0"
+ ".": "0.120.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index ec91ee34..8e11d075 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 172
+configured_endpoints: 174
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a874179c..976a2d24 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## [0.120.0](https://github.com/kernel/kernel-python-sdk/compare/v0.119.0...v0.120.0) (2026-10-06)
+
+
+### Features
+
+* Accept network.allowed_hosts on browser create behind an org flag ([3178829](https://github.com/kernel/kernel-python-sdk/commit/3178829222a373a69fdb7617abcab02f409d2d6e))
+* Add us-west placement region support ([915eb0d](https://github.com/kernel/kernel-python-sdk/commit/915eb0d4c9fc70df904515df6f3e07ce6c1ac2c0))
+* Add Visa passkey approval for VGS purchases ([fadc7c9](https://github.com/kernel/kernel-python-sdk/commit/fadc7c95c7fe45df163e6f186384a7d534c8c014))
+* Connect Kernel wallets once the card is stored, with best-effort network tokens ([da49f00](https://github.com/kernel/kernel-python-sdk/commit/da49f007e5b00cdca00f08500db43bfc6332c4c3))
+* Enable Exa highlights and default Perplexity to medium context ([761b766](https://github.com/kernel/kernel-python-sdk/commit/761b766f80e9ccfabf65a412bf777e5a861b400b))
+* Expose named Playwright executors on the browser API ([8c80fc1](https://github.com/kernel/kernel-python-sdk/commit/8c80fc14441a149e4c6b5cd5e7a9a7a1db4706c2))
+
## [0.119.0](https://github.com/kernel/kernel-python-sdk/compare/v0.118.0...v0.119.0) (2026-10-02)
diff --git a/api.md b/api.md
index 51c1a9ea..798034d1 100644
--- a/api.md
+++ b/api.md
@@ -334,12 +334,25 @@ Methods:
Types:
```python
-from kernel.types.browsers import PlaywrightExecuteResponse
+from kernel.types.browsers import ExecutorLimitError, Tab, PlaywrightExecuteResponse
```
Methods:
-- client.browsers.playwright.execute(id_or_name, \*\*params) -> PlaywrightExecuteResponse
+- client.browsers.playwright.execute(id_or_name, \*\*params) -> PlaywrightExecuteResponse
+
+### Executors
+
+Types:
+
+```python
+from kernel.types.browsers.playwright import Executor, ExecutorList
+```
+
+Methods:
+
+- client.browsers.playwright.executors.list(id_or_name) -> ExecutorList
+- client.browsers.playwright.executors.delete(name, \*, id_or_name, \*\*params) -> None
## Webmcp
diff --git a/pyproject.toml b/pyproject.toml
index aa2a10b2..7f92defc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "kernel"
-version = "0.119.0"
+version = "0.120.0"
description = "The official Python library for the kernel API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/kernel/_version.py b/src/kernel/_version.py
index fb178235..a819522f 100644
--- a/src/kernel/_version.py
+++ b/src/kernel/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "kernel"
-__version__ = "0.119.0" # x-release-please-version
+__version__ = "0.120.0" # x-release-please-version
diff --git a/src/kernel/resources/browser_pools.py b/src/kernel/resources/browser_pools.py
index 20fdc7fa..6c34209e 100644
--- a/src/kernel/resources/browser_pools.py
+++ b/src/kernel/resources/browser_pools.py
@@ -77,7 +77,7 @@ def create(
profile: browser_pool_create_params.Profile | Omit = omit,
proxy_id: str | Omit = omit,
refresh_on_profile_update: bool | Omit = omit,
- region: Literal["us-east", "eu-west", "ap-southeast"] | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
start_url: str | Omit = omit,
stealth: bool | Omit = omit,
telemetry: Optional[browser_pool_create_params.Telemetry] | Omit = omit,
@@ -419,7 +419,7 @@ def list(
name: str | Omit = omit,
offset: int | Omit = omit,
query: str | Omit = omit,
- region: Literal["us-east", "eu-west", "ap-southeast"] | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | 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,
@@ -724,7 +724,7 @@ async def create(
profile: browser_pool_create_params.Profile | Omit = omit,
proxy_id: str | Omit = omit,
refresh_on_profile_update: bool | Omit = omit,
- region: Literal["us-east", "eu-west", "ap-southeast"] | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
start_url: str | Omit = omit,
stealth: bool | Omit = omit,
telemetry: Optional[browser_pool_create_params.Telemetry] | Omit = omit,
@@ -1066,7 +1066,7 @@ def list(
name: str | Omit = omit,
offset: int | Omit = omit,
query: str | Omit = omit,
- region: Literal["us-east", "eu-west", "ap-southeast"] | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | 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,
diff --git a/src/kernel/resources/browsers/browsers.py b/src/kernel/resources/browsers/browsers.py
index b1cf74d6..289368a3 100644
--- a/src/kernel/resources/browsers/browsers.py
+++ b/src/kernel/resources/browsers/browsers.py
@@ -70,14 +70,6 @@
TelemetryResourceWithStreamingResponse,
AsyncTelemetryResourceWithStreamingResponse,
)
-from .playwright import (
- PlaywrightResource,
- AsyncPlaywrightResource,
- PlaywrightResourceWithRawResponse,
- AsyncPlaywrightResourceWithRawResponse,
- PlaywrightResourceWithStreamingResponse,
- AsyncPlaywrightResourceWithStreamingResponse,
-)
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
to_raw_response_wrapper,
@@ -96,6 +88,14 @@
)
from ..._base_client import AsyncPaginator, make_request_options
from ...types.tags_param import TagsParam
+from .playwright.playwright import (
+ PlaywrightResource,
+ AsyncPlaywrightResource,
+ PlaywrightResourceWithRawResponse,
+ AsyncPlaywrightResourceWithRawResponse,
+ PlaywrightResourceWithStreamingResponse,
+ AsyncPlaywrightResourceWithStreamingResponse,
+)
from ...types.browser_repl_result import BrowserReplResult
from ...types.browser_curl_response import BrowserCurlResponse
from ...types.browser_list_response import BrowserListResponse
@@ -154,7 +154,9 @@ def computer(self) -> ComputerResource:
@cached_property
def playwright(self) -> PlaywrightResource:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return PlaywrightResource(self._client)
@cached_property
@@ -196,7 +198,7 @@ def create(
profile: BrowserProfile | Omit = omit,
proxy: BrowserProxyConfigParam | Omit = omit,
proxy_id: str | Omit = omit,
- region: Literal["us-east", "eu-west", "ap-southeast"] | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
start_url: str | Omit = omit,
stealth: bool | Omit = omit,
tags: TagsParam | Omit = omit,
@@ -485,7 +487,7 @@ def list(
limit: int | Omit = omit,
offset: int | Omit = omit,
query: str | Omit = omit,
- region: Literal["us-east", "eu-west", "ap-southeast"] | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
status: Literal["active", "deleted", "all"] | Omit = omit,
tags: Dict[str, str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -856,7 +858,9 @@ def computer(self) -> AsyncComputerResource:
@cached_property
def playwright(self) -> AsyncPlaywrightResource:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return AsyncPlaywrightResource(self._client)
@cached_property
@@ -898,7 +902,7 @@ async def create(
profile: BrowserProfile | Omit = omit,
proxy: BrowserProxyConfigParam | Omit = omit,
proxy_id: str | Omit = omit,
- region: Literal["us-east", "eu-west", "ap-southeast"] | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
start_url: str | Omit = omit,
stealth: bool | Omit = omit,
tags: TagsParam | Omit = omit,
@@ -1187,7 +1191,7 @@ def list(
limit: int | Omit = omit,
offset: int | Omit = omit,
query: str | Omit = omit,
- region: Literal["us-east", "eu-west", "ap-southeast"] | Omit = omit,
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"] | Omit = omit,
status: Literal["active", "deleted", "all"] | Omit = omit,
tags: Dict[str, str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -1586,7 +1590,9 @@ def computer(self) -> ComputerResourceWithRawResponse:
@cached_property
def playwright(self) -> PlaywrightResourceWithRawResponse:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return PlaywrightResourceWithRawResponse(self._browsers.playwright)
@cached_property
@@ -1658,7 +1664,9 @@ def computer(self) -> AsyncComputerResourceWithRawResponse:
@cached_property
def playwright(self) -> AsyncPlaywrightResourceWithRawResponse:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return AsyncPlaywrightResourceWithRawResponse(self._browsers.playwright)
@cached_property
@@ -1730,7 +1738,9 @@ def computer(self) -> ComputerResourceWithStreamingResponse:
@cached_property
def playwright(self) -> PlaywrightResourceWithStreamingResponse:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return PlaywrightResourceWithStreamingResponse(self._browsers.playwright)
@cached_property
@@ -1802,7 +1812,9 @@ def computer(self) -> AsyncComputerResourceWithStreamingResponse:
@cached_property
def playwright(self) -> AsyncPlaywrightResourceWithStreamingResponse:
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
return AsyncPlaywrightResourceWithStreamingResponse(self._browsers.playwright)
@cached_property
diff --git a/src/kernel/resources/browsers/playwright/__init__.py b/src/kernel/resources/browsers/playwright/__init__.py
new file mode 100644
index 00000000..fa98144e
--- /dev/null
+++ b/src/kernel/resources/browsers/playwright/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .executors import (
+ ExecutorsResource,
+ AsyncExecutorsResource,
+ ExecutorsResourceWithRawResponse,
+ AsyncExecutorsResourceWithRawResponse,
+ ExecutorsResourceWithStreamingResponse,
+ AsyncExecutorsResourceWithStreamingResponse,
+)
+from .playwright import (
+ PlaywrightResource,
+ AsyncPlaywrightResource,
+ PlaywrightResourceWithRawResponse,
+ AsyncPlaywrightResourceWithRawResponse,
+ PlaywrightResourceWithStreamingResponse,
+ AsyncPlaywrightResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "ExecutorsResource",
+ "AsyncExecutorsResource",
+ "ExecutorsResourceWithRawResponse",
+ "AsyncExecutorsResourceWithRawResponse",
+ "ExecutorsResourceWithStreamingResponse",
+ "AsyncExecutorsResourceWithStreamingResponse",
+ "PlaywrightResource",
+ "AsyncPlaywrightResource",
+ "PlaywrightResourceWithRawResponse",
+ "AsyncPlaywrightResourceWithRawResponse",
+ "PlaywrightResourceWithStreamingResponse",
+ "AsyncPlaywrightResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/browsers/playwright/executors.py b/src/kernel/resources/browsers/playwright/executors.py
new file mode 100644
index 00000000..11d78f10
--- /dev/null
+++ b/src/kernel/resources/browsers/playwright/executors.py
@@ -0,0 +1,307 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ...._utils import path_template, maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.browsers.playwright import executor_delete_params
+from ....types.browsers.playwright.executor_list import ExecutorList
+
+__all__ = ["ExecutorsResource", "AsyncExecutorsResource"]
+
+
+class ExecutorsResource(SyncAPIResource):
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> ExecutorsResourceWithRawResponse:
+ """
+ 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/kernel/kernel-python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return ExecutorsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ExecutorsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/kernel/kernel-python-sdk#with_streaming_response
+ """
+ return ExecutorsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ id_or_name: 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,
+ ) -> ExecutorList:
+ """
+ Lists the default executor first, then the named executors created by POST
+ /browsers/{id_or_name}/playwright/execute. Each entry reports whether a call is
+ running on it and, for named executors, the target ID and URL of the tab it
+ owns. Returns 404 for a browser whose image predates executors.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ return self._get(
+ path_template("/browsers/{id_or_name}/playwright/executors", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ExecutorList,
+ )
+
+ def delete(
+ self,
+ name: str,
+ *,
+ id_or_name: str,
+ close_tab: bool | 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,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Stops the executor's process.
+
+ A call running on it fails with an error saying
+ the executor was deleted. By default the executor's tab is closed too. The name
+ can be reused; the next call with it creates a new executor and tab.
+
+ The default executor is restarted instead of removed: its process is stopped, a
+ call running on it fails with an error saying the executor was restarted, and
+ queued and later calls run on a new process. It owns no tab, so 'close_tab' has
+ no effect on it.
+
+ Args:
+ name: Name of a Playwright executor. Calls with the same name run in the same
+ executor, one at a time; the first call with a new name creates it. Calls on
+ different executors run concurrently. 'default' names the executor that runs
+ calls without a name.
+
+ close_tab: Close the executor's tab. Defaults to true.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not name:
+ raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/browsers/{id_or_name}/playwright/executors/{name}", id_or_name=id_or_name, name=name),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"close_tab": close_tab}, executor_delete_params.ExecutorDeleteParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncExecutorsResource(AsyncAPIResource):
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncExecutorsResourceWithRawResponse:
+ """
+ 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/kernel/kernel-python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncExecutorsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncExecutorsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/kernel/kernel-python-sdk#with_streaming_response
+ """
+ return AsyncExecutorsResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ id_or_name: 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,
+ ) -> ExecutorList:
+ """
+ Lists the default executor first, then the named executors created by POST
+ /browsers/{id_or_name}/playwright/execute. Each entry reports whether a call is
+ running on it and, for named executors, the target ID and URL of the tab it
+ owns. Returns 404 for a browser whose image predates executors.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ return await self._get(
+ path_template("/browsers/{id_or_name}/playwright/executors", id_or_name=id_or_name),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ExecutorList,
+ )
+
+ async def delete(
+ self,
+ name: str,
+ *,
+ id_or_name: str,
+ close_tab: bool | 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,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Stops the executor's process.
+
+ A call running on it fails with an error saying
+ the executor was deleted. By default the executor's tab is closed too. The name
+ can be reused; the next call with it creates a new executor and tab.
+
+ The default executor is restarted instead of removed: its process is stopped, a
+ call running on it fails with an error saying the executor was restarted, and
+ queued and later calls run on a new process. It owns no tab, so 'close_tab' has
+ no effect on it.
+
+ Args:
+ name: Name of a Playwright executor. Calls with the same name run in the same
+ executor, one at a time; the first call with a new name creates it. Calls on
+ different executors run concurrently. 'default' names the executor that runs
+ calls without a name.
+
+ close_tab: Close the executor's tab. Defaults to true.
+
+ 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_or_name:
+ raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}")
+ if not name:
+ raise ValueError(f"Expected a non-empty value for `name` but received {name!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/browsers/{id_or_name}/playwright/executors/{name}", id_or_name=id_or_name, name=name),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"close_tab": close_tab}, executor_delete_params.ExecutorDeleteParams
+ ),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class ExecutorsResourceWithRawResponse:
+ def __init__(self, executors: ExecutorsResource) -> None:
+ self._executors = executors
+
+ self.list = to_raw_response_wrapper(
+ executors.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ executors.delete,
+ )
+
+
+class AsyncExecutorsResourceWithRawResponse:
+ def __init__(self, executors: AsyncExecutorsResource) -> None:
+ self._executors = executors
+
+ self.list = async_to_raw_response_wrapper(
+ executors.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ executors.delete,
+ )
+
+
+class ExecutorsResourceWithStreamingResponse:
+ def __init__(self, executors: ExecutorsResource) -> None:
+ self._executors = executors
+
+ self.list = to_streamed_response_wrapper(
+ executors.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ executors.delete,
+ )
+
+
+class AsyncExecutorsResourceWithStreamingResponse:
+ def __init__(self, executors: AsyncExecutorsResource) -> None:
+ self._executors = executors
+
+ self.list = async_to_streamed_response_wrapper(
+ executors.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ executors.delete,
+ )
diff --git a/src/kernel/resources/browsers/playwright.py b/src/kernel/resources/browsers/playwright/playwright.py
similarity index 50%
rename from src/kernel/resources/browsers/playwright.py
rename to src/kernel/resources/browsers/playwright/playwright.py
index c1f9cea5..50ddf810 100644
--- a/src/kernel/resources/browsers/playwright.py
+++ b/src/kernel/resources/browsers/playwright/playwright.py
@@ -4,25 +4,42 @@
import httpx
-from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
-from ..._compat import cached_property
-from ..._resource import SyncAPIResource, AsyncAPIResource
-from ..._response import (
+from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ...._utils import path_template, maybe_transform, async_maybe_transform
+from .executors import (
+ ExecutorsResource,
+ AsyncExecutorsResource,
+ ExecutorsResourceWithRawResponse,
+ AsyncExecutorsResourceWithRawResponse,
+ ExecutorsResourceWithStreamingResponse,
+ AsyncExecutorsResourceWithStreamingResponse,
+)
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
to_raw_response_wrapper,
to_streamed_response_wrapper,
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
-from ..._base_client import make_request_options
-from ...types.browsers import playwright_execute_params
-from ...types.browsers.playwright_execute_response import PlaywrightExecuteResponse
+from ...._base_client import make_request_options
+from ....types.browsers import playwright_execute_params
+from ....types.browsers.playwright_execute_response import PlaywrightExecuteResponse
__all__ = ["PlaywrightResource", "AsyncPlaywrightResource"]
class PlaywrightResource(SyncAPIResource):
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+
+ @cached_property
+ def executors(self) -> ExecutorsResource:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return ExecutorsResource(self._client)
@cached_property
def with_raw_response(self) -> PlaywrightResourceWithRawResponse:
@@ -48,6 +65,7 @@ def execute(
id_or_name: str,
*,
code: str,
+ executor: str | Omit = omit,
timeout_sec: int | 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.
@@ -65,6 +83,41 @@ def execute(
exact registration. It can `return` a value, and this value is returned in the
response.
+ Every call runs in an executor: a dedicated Node.js process with its own browser
+ connection. Calls on different executors run concurrently; calls on the same
+ executor run one at a time. A timeout, crash, or blocked event loop in one
+ executor does not affect other executors. After a timeout the executor keeps its
+ process and drops its browser connection, so code abandoned by the timeout
+ cannot keep driving the browser. After a crash or a blocked event loop, the next
+ call on that executor starts a fresh process.
+
+ Calls without 'executor' run in the executor named 'default', which always
+ exists and is the same as passing 'executor: "default"'. In the default
+ executor, 'page' is bound to an active tab reported by Chrome. In single-window
+ sessions, this is the foreground tab. When multiple browser windows are open,
+ Chrome reports one active tab per window and the selected window is unspecified.
+ 'context' is the BrowserContext that owns the selected page. Use
+ 'browser.contexts()' to select a context or page explicitly.
+
+ Pass any other name to run the call in a named executor. The first call with a
+ new name creates it. Each named executor owns a tab: its first call opens a new
+ background tab in the default browser context, and 'page' is bound to that tab
+ on every later call while it stays open. Opening it does not change the active
+ tab of an existing window. If the tab is closed, the next call opens a new one
+ and reports 'tab.created: true'. Executor code can still reach other tabs
+ through 'context' and 'browser'; ownership only decides what 'page' is bound to.
+ Use named executors to drive several tabs of one browser in parallel.
+
+ A browser can have at most 8 named executors; the default executor does not
+ count. A call that would create another returns 409 with the current executors;
+ delete one with DELETE /browsers/{id_or_name}/playwright/executors/{name}. Named
+ executors are not removed automatically while the browser runs; when it shuts
+ down, they are removed and their tabs closed.
+
+ A named call to a browser whose image predates executors fails with 400 instead
+ of running on the active tab; calls without 'executor' keep working on every
+ image.
+
Args:
code: TypeScript/JavaScript code to execute. The code has access to 'page', 'context',
and 'browser' variables. It runs within a function, so you can use a return
@@ -72,6 +125,11 @@ def execute(
property in the response. Example: "await page.goto('https://example.com');
return await page.title();"
+ executor: Name of a Playwright executor. Calls with the same name run in the same
+ executor, one at a time; the first call with a new name creates it. Calls on
+ different executors run concurrently. 'default' names the executor that runs
+ calls without a name.
+
timeout_sec: Maximum execution time in seconds. Default is 60.
extra_headers: Send extra headers
@@ -89,6 +147,7 @@ def execute(
body=maybe_transform(
{
"code": code,
+ "executor": executor,
"timeout_sec": timeout_sec,
},
playwright_execute_params.PlaywrightExecuteParams,
@@ -101,7 +160,16 @@ def execute(
class AsyncPlaywrightResource(AsyncAPIResource):
- """Execute Playwright code against the browser instance."""
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+
+ @cached_property
+ def executors(self) -> AsyncExecutorsResource:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return AsyncExecutorsResource(self._client)
@cached_property
def with_raw_response(self) -> AsyncPlaywrightResourceWithRawResponse:
@@ -127,6 +195,7 @@ async def execute(
id_or_name: str,
*,
code: str,
+ executor: str | Omit = omit,
timeout_sec: int | 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.
@@ -144,6 +213,41 @@ async def execute(
exact registration. It can `return` a value, and this value is returned in the
response.
+ Every call runs in an executor: a dedicated Node.js process with its own browser
+ connection. Calls on different executors run concurrently; calls on the same
+ executor run one at a time. A timeout, crash, or blocked event loop in one
+ executor does not affect other executors. After a timeout the executor keeps its
+ process and drops its browser connection, so code abandoned by the timeout
+ cannot keep driving the browser. After a crash or a blocked event loop, the next
+ call on that executor starts a fresh process.
+
+ Calls without 'executor' run in the executor named 'default', which always
+ exists and is the same as passing 'executor: "default"'. In the default
+ executor, 'page' is bound to an active tab reported by Chrome. In single-window
+ sessions, this is the foreground tab. When multiple browser windows are open,
+ Chrome reports one active tab per window and the selected window is unspecified.
+ 'context' is the BrowserContext that owns the selected page. Use
+ 'browser.contexts()' to select a context or page explicitly.
+
+ Pass any other name to run the call in a named executor. The first call with a
+ new name creates it. Each named executor owns a tab: its first call opens a new
+ background tab in the default browser context, and 'page' is bound to that tab
+ on every later call while it stays open. Opening it does not change the active
+ tab of an existing window. If the tab is closed, the next call opens a new one
+ and reports 'tab.created: true'. Executor code can still reach other tabs
+ through 'context' and 'browser'; ownership only decides what 'page' is bound to.
+ Use named executors to drive several tabs of one browser in parallel.
+
+ A browser can have at most 8 named executors; the default executor does not
+ count. A call that would create another returns 409 with the current executors;
+ delete one with DELETE /browsers/{id_or_name}/playwright/executors/{name}. Named
+ executors are not removed automatically while the browser runs; when it shuts
+ down, they are removed and their tabs closed.
+
+ A named call to a browser whose image predates executors fails with 400 instead
+ of running on the active tab; calls without 'executor' keep working on every
+ image.
+
Args:
code: TypeScript/JavaScript code to execute. The code has access to 'page', 'context',
and 'browser' variables. It runs within a function, so you can use a return
@@ -151,6 +255,11 @@ async def execute(
property in the response. Example: "await page.goto('https://example.com');
return await page.title();"
+ executor: Name of a Playwright executor. Calls with the same name run in the same
+ executor, one at a time; the first call with a new name creates it. Calls on
+ different executors run concurrently. 'default' names the executor that runs
+ calls without a name.
+
timeout_sec: Maximum execution time in seconds. Default is 60.
extra_headers: Send extra headers
@@ -168,6 +277,7 @@ async def execute(
body=await async_maybe_transform(
{
"code": code,
+ "executor": executor,
"timeout_sec": timeout_sec,
},
playwright_execute_params.PlaywrightExecuteParams,
@@ -187,6 +297,13 @@ def __init__(self, playwright: PlaywrightResource) -> None:
playwright.execute,
)
+ @cached_property
+ def executors(self) -> ExecutorsResourceWithRawResponse:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return ExecutorsResourceWithRawResponse(self._playwright.executors)
+
class AsyncPlaywrightResourceWithRawResponse:
def __init__(self, playwright: AsyncPlaywrightResource) -> None:
@@ -196,6 +313,13 @@ def __init__(self, playwright: AsyncPlaywrightResource) -> None:
playwright.execute,
)
+ @cached_property
+ def executors(self) -> AsyncExecutorsResourceWithRawResponse:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return AsyncExecutorsResourceWithRawResponse(self._playwright.executors)
+
class PlaywrightResourceWithStreamingResponse:
def __init__(self, playwright: PlaywrightResource) -> None:
@@ -205,6 +329,13 @@ def __init__(self, playwright: PlaywrightResource) -> None:
playwright.execute,
)
+ @cached_property
+ def executors(self) -> ExecutorsResourceWithStreamingResponse:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return ExecutorsResourceWithStreamingResponse(self._playwright.executors)
+
class AsyncPlaywrightResourceWithStreamingResponse:
def __init__(self, playwright: AsyncPlaywrightResource) -> None:
@@ -213,3 +344,10 @@ def __init__(self, playwright: AsyncPlaywrightResource) -> None:
self.execute = async_to_streamed_response_wrapper(
playwright.execute,
)
+
+ @cached_property
+ def executors(self) -> AsyncExecutorsResourceWithStreamingResponse:
+ """
+ Execute Playwright code against the browser instance and manage the executors it runs in.
+ """
+ return AsyncExecutorsResourceWithStreamingResponse(self._playwright.executors)
diff --git a/src/kernel/types/auth/managed_auth_browser_config.py b/src/kernel/types/auth/managed_auth_browser_config.py
index 1cbab85a..0de4499d 100644
--- a/src/kernel/types/auth/managed_auth_browser_config.py
+++ b/src/kernel/types/auth/managed_auth_browser_config.py
@@ -124,7 +124,7 @@ class ManagedAuthBrowserConfig(BaseModel):
preserve or inherit the connection default.
"""
- region: Optional[Literal["us-east", "eu-west", "ap-southeast"]] = None
+ region: Optional[Literal["us-east", "us-west", "eu-west", "ap-southeast"]] = None
"""Browser region.
Omit on create to use us-east, on update to keep the current region, or on login
diff --git a/src/kernel/types/auth/managed_auth_browser_config_param.py b/src/kernel/types/auth/managed_auth_browser_config_param.py
index 47651227..98577698 100644
--- a/src/kernel/types/auth/managed_auth_browser_config_param.py
+++ b/src/kernel/types/auth/managed_auth_browser_config_param.py
@@ -125,7 +125,7 @@ class ManagedAuthBrowserConfigParam(TypedDict, total=False):
preserve or inherit the connection default.
"""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Browser region.
Omit on create to use us-east, on update to keep the current region, or on login
diff --git a/src/kernel/types/browser_create_params.py b/src/kernel/types/browser_create_params.py
index fd33cee5..e77d2be1 100644
--- a/src/kernel/types/browser_create_params.py
+++ b/src/kernel/types/browser_create_params.py
@@ -103,7 +103,7 @@ class BrowserCreateParams(TypedDict, total=False):
favor of proxy.
"""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Geographic region for the browser session.
It is fixed once the session is created. Region selection requires a Start-Up or
diff --git a/src/kernel/types/browser_create_response.py b/src/kernel/types/browser_create_response.py
index a5e674f7..09c88f9a 100644
--- a/src/kernel/types/browser_create_response.py
+++ b/src/kernel/types/browser_create_response.py
@@ -32,7 +32,7 @@ class BrowserCreateResponse(BaseModel):
memory: BrowserMemory
"""Memory allocated to the browser session."""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Geographic region of the browser session. Fixed once the session is created."""
session_id: str
diff --git a/src/kernel/types/browser_list_params.py b/src/kernel/types/browser_list_params.py
index eb5b91dc..5bf673b5 100644
--- a/src/kernel/types/browser_list_params.py
+++ b/src/kernel/types/browser_list_params.py
@@ -27,7 +27,7 @@ class BrowserListParams(TypedDict, total=False):
Search browsers by name, session ID, profile name or ID, proxy ID, or pool name.
"""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Filter sessions by geographic region. Omit to list sessions in all regions."""
status: Literal["active", "deleted", "all"]
diff --git a/src/kernel/types/browser_list_response.py b/src/kernel/types/browser_list_response.py
index 3cc62ebe..6f1aad11 100644
--- a/src/kernel/types/browser_list_response.py
+++ b/src/kernel/types/browser_list_response.py
@@ -32,7 +32,7 @@ class BrowserListResponse(BaseModel):
memory: BrowserMemory
"""Memory allocated to the browser session."""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Geographic region of the browser session. Fixed once the session is created."""
session_id: str
diff --git a/src/kernel/types/browser_network_config.py b/src/kernel/types/browser_network_config.py
index ec2c7fbf..453db6bd 100644
--- a/src/kernel/types/browser_network_config.py
+++ b/src/kernel/types/browser_network_config.py
@@ -31,6 +31,29 @@ class ProxyRoute(BaseModel):
class BrowserNetworkConfig(BaseModel):
"""Network configuration for a browser session or browser pool."""
+ allowed_hosts: Optional[List[str]] = None
+ """
+ Egress allowlist for a browser session: the only destinations the browser may
+ reach through Kernel-managed egress. Any other destination is refused with a 403
+ whose X-Kernel-Proxy-Error header is network_policy_denied, so pages cannot load
+ or send data to unlisted hosts, including with fetch() and WebSockets. Omit the
+ field for unfiltered egress; an empty list is invalid. The allowlist applies
+ from the browser's first request, and start_url must be allowed by it. Entries
+ are exact hostnames ("example.com"), one leading "_." wildcard that matches
+ subdomains at any depth but not the domain itself ("_.example.com" matches
+ api.example.com, not example.com), public IPv4 addresses ("8.8.8.8"), bracketed
+ public IPv6 addresses ("[2001:4860:4860::8888]"), or public CIDR ranges in
+ canonical form ("8.8.4.0/24", "2001:4860::/32"). IP and CIDR entries only match
+ destinations written as an IP address, never hostnames that resolve into the
+ range. Entries cannot include ports, paths, or schemes, and match every port on
+ their host. Wildcards over a public suffix ("_.com", "_.github.io") and private
+ or reserved IP ranges are rejected, as are entries that overlap private_hosts.
+ Enforced at Kernel's egress proxy only: destinations in private_hosts, and
+ processes in the browser VM that do not use the browser's proxy, are not
+ filtered, and Kernel's own control traffic is always allowed. Requires proxy v3.
+ Not supported on browser pools.
+ """
+
private_hosts: Optional[List[str]] = None
"""
Destinations the browser reaches directly through the session's own network
diff --git a/src/kernel/types/browser_network_config_param.py b/src/kernel/types/browser_network_config_param.py
index 298f9d13..c643b6ae 100644
--- a/src/kernel/types/browser_network_config_param.py
+++ b/src/kernel/types/browser_network_config_param.py
@@ -34,6 +34,29 @@ class ProxyRoute(TypedDict, total=False):
class BrowserNetworkConfigParam(TypedDict, total=False):
"""Network configuration for a browser session or browser pool."""
+ allowed_hosts: SequenceNotStr[str]
+ """
+ Egress allowlist for a browser session: the only destinations the browser may
+ reach through Kernel-managed egress. Any other destination is refused with a 403
+ whose X-Kernel-Proxy-Error header is network_policy_denied, so pages cannot load
+ or send data to unlisted hosts, including with fetch() and WebSockets. Omit the
+ field for unfiltered egress; an empty list is invalid. The allowlist applies
+ from the browser's first request, and start_url must be allowed by it. Entries
+ are exact hostnames ("example.com"), one leading "_." wildcard that matches
+ subdomains at any depth but not the domain itself ("_.example.com" matches
+ api.example.com, not example.com), public IPv4 addresses ("8.8.8.8"), bracketed
+ public IPv6 addresses ("[2001:4860:4860::8888]"), or public CIDR ranges in
+ canonical form ("8.8.4.0/24", "2001:4860::/32"). IP and CIDR entries only match
+ destinations written as an IP address, never hostnames that resolve into the
+ range. Entries cannot include ports, paths, or schemes, and match every port on
+ their host. Wildcards over a public suffix ("_.com", "_.github.io") and private
+ or reserved IP ranges are rejected, as are entries that overlap private_hosts.
+ Enforced at Kernel's egress proxy only: destinations in private_hosts, and
+ processes in the browser VM that do not use the browser's proxy, are not
+ filtered, and Kernel's own control traffic is always allowed. Requires proxy v3.
+ Not supported on browser pools.
+ """
+
private_hosts: SequenceNotStr[str]
"""
Destinations the browser reaches directly through the session's own network
diff --git a/src/kernel/types/browser_pool.py b/src/kernel/types/browser_pool.py
index 58a1a9cd..035b5722 100644
--- a/src/kernel/types/browser_pool.py
+++ b/src/kernel/types/browser_pool.py
@@ -178,7 +178,7 @@ class BrowserPool(BaseModel):
(echoed as sent on create).
"""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Geographic region of the browser pool. Fixed once the pool is created."""
name: Optional[str] = None
diff --git a/src/kernel/types/browser_pool_acquire_response.py b/src/kernel/types/browser_pool_acquire_response.py
index c223c927..a0617cb6 100644
--- a/src/kernel/types/browser_pool_acquire_response.py
+++ b/src/kernel/types/browser_pool_acquire_response.py
@@ -32,7 +32,7 @@ class BrowserPoolAcquireResponse(BaseModel):
memory: BrowserMemory
"""Memory allocated to the browser session."""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Geographic region of the browser session. Fixed once the session is created."""
session_id: str
diff --git a/src/kernel/types/browser_pool_create_params.py b/src/kernel/types/browser_pool_create_params.py
index fa1f5f89..3929b44c 100644
--- a/src/kernel/types/browser_pool_create_params.py
+++ b/src/kernel/types/browser_pool_create_params.py
@@ -92,7 +92,7 @@ class BrowserPoolCreateParams(TypedDict, total=False):
creation, this defaults to true. Requires a profile to be set on the pool.
"""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Geographic region for the browser pool.
It is fixed once the pool is created. Region selection requires a Start-Up or
diff --git a/src/kernel/types/browser_pool_list_params.py b/src/kernel/types/browser_pool_list_params.py
index 7d7a268c..8f9961f4 100644
--- a/src/kernel/types/browser_pool_list_params.py
+++ b/src/kernel/types/browser_pool_list_params.py
@@ -28,5 +28,5 @@ class BrowserPoolListParams(TypedDict, total=False):
IDs match by exact value.
"""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Filter pools by geographic region. Omit to list pools in all regions."""
diff --git a/src/kernel/types/browser_retrieve_response.py b/src/kernel/types/browser_retrieve_response.py
index 936b41c6..1642e55e 100644
--- a/src/kernel/types/browser_retrieve_response.py
+++ b/src/kernel/types/browser_retrieve_response.py
@@ -32,7 +32,7 @@ class BrowserRetrieveResponse(BaseModel):
memory: BrowserMemory
"""Memory allocated to the browser session."""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Geographic region of the browser session. Fixed once the session is created."""
session_id: str
diff --git a/src/kernel/types/browser_update_response.py b/src/kernel/types/browser_update_response.py
index d2296c21..98484751 100644
--- a/src/kernel/types/browser_update_response.py
+++ b/src/kernel/types/browser_update_response.py
@@ -32,7 +32,7 @@ class BrowserUpdateResponse(BaseModel):
memory: BrowserMemory
"""Memory allocated to the browser session."""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Geographic region of the browser session. Fixed once the session is created."""
session_id: str
diff --git a/src/kernel/types/browsers/__init__.py b/src/kernel/types/browsers/__init__.py
index 2decde3a..27104cff 100644
--- a/src/kernel/types/browsers/__init__.py
+++ b/src/kernel/types/browsers/__init__.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+from .tab import Tab as Tab
from .tool import Tool as Tool
from .tool_frame import ToolFrame as ToolFrame
from .tool_source import ToolSource as ToolSource
diff --git a/src/kernel/types/browsers/browser_proxy_error_event.py b/src/kernel/types/browsers/browser_proxy_error_event.py
index 44961139..574f525d 100644
--- a/src/kernel/types/browsers/browser_proxy_error_event.py
+++ b/src/kernel/types/browsers/browser_proxy_error_event.py
@@ -19,6 +19,7 @@ class Data(BrowserEventContext):
code: Literal[
"destination_blocked",
"destination_route_unavailable",
+ "network_policy_denied",
"provider_blacklisted",
"provider_unreachable",
"provider_rejected",
@@ -33,20 +34,23 @@ class Data(BrowserEventContext):
]
"""
Proxy-layer error code: the X-Kernel-Proxy-Error response header value from a
- branded 5xx error page served by the metro egress host-proxy. Values mirror what
- the proxy emits: destination_blocked, destination_route_unavailable,
- provider_blacklisted, provider_unreachable, provider_rejected,
- origin_tls_timeout, origin_response_incomplete, proxy_unavailable,
- restricted_route_unavailable, upstream_timeout, upstream_dns_failure,
- upstream_connect_failed. A header value the browser image does not recognize is
- reported as unknown, with the header value in raw_code.
+ branded error page served by the metro egress host-proxy. Values mirror what the
+ proxy emits: destination_blocked, destination_route_unavailable,
+ network_policy_denied, provider_blacklisted, provider_unreachable,
+ provider_rejected, origin_tls_timeout, origin_response_incomplete,
+ proxy_unavailable, restricted_route_unavailable, upstream_timeout,
+ upstream_dns_failure, upstream_connect_failed. A header value the browser image
+ does not recognize is reported as unknown, with the header value in raw_code.
"""
request_id: str
"""CDP request identifier matching the originating request."""
status: int
- """HTTP response status of the branded error page (502)."""
+ """
+ HTTP response status of the branded error page (502, or 403 for
+ network_policy_denied).
+ """
method: Optional[str] = None
"""HTTP method of the failed request, when known."""
@@ -66,7 +70,7 @@ class Data(BrowserEventContext):
class BrowserProxyErrorEvent(BaseModel):
"""A branded proxy-layer failure observed by the browser.
- Emitted when the metro egress host-proxy serves a branded 5xx error page whose response carries the X-Kernel-Proxy-Error header. Low-volume and carries a typed code. Its value is per-session and per-URL attribution for sessions that already capture the network stream: proxy failures are only observable while the CDP network collector is running, so this is an opt-in refinement of the raw network events rather than a default-on alerting signal.
+ Emitted when the metro egress host-proxy serves a branded error page whose response carries the X-Kernel-Proxy-Error header: a 502 for proxy-layer failures, or a 403 when the session's network policy blocks the destination. Low-volume and carries a typed code. Its value is per-session and per-URL attribution for sessions that already capture the network stream: proxy failures are only observable while the CDP network collector is running, so this is an opt-in refinement of the raw network events rather than a default-on alerting signal.
"""
category: Literal["network"]
diff --git a/src/kernel/types/browsers/playwright/__init__.py b/src/kernel/types/browsers/playwright/__init__.py
new file mode 100644
index 00000000..74b73b59
--- /dev/null
+++ b/src/kernel/types/browsers/playwright/__init__.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .executor import Executor as Executor
+from .executor_list import ExecutorList as ExecutorList
+from .executor_delete_params import ExecutorDeleteParams as ExecutorDeleteParams
diff --git a/src/kernel/types/browsers/playwright/executor.py b/src/kernel/types/browsers/playwright/executor.py
new file mode 100644
index 00000000..0085af9a
--- /dev/null
+++ b/src/kernel/types/browsers/playwright/executor.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from ...._models import BaseModel
+
+__all__ = ["Executor"]
+
+
+class Executor(BaseModel):
+ """A Playwright executor on the browser"""
+
+ busy: bool
+ """Whether a call is running on the executor"""
+
+ created_at: datetime
+ """When the executor was created"""
+
+ last_used_at: datetime
+ """When the most recent call on the executor started"""
+
+ name: str
+ """Name of a Playwright executor.
+
+ Calls with the same name run in the same executor, one at a time; the first call
+ with a new name creates it. Calls on different executors run concurrently.
+ 'default' names the executor that runs calls without a name.
+ """
+
+ target_id: Optional[str] = None
+ """CDP page target ID of the executor's tab, once it has one.
+
+ The default executor owns no tab.
+ """
+
+ url: Optional[str] = None
+ """Current URL of the executor's tab, when it is open"""
diff --git a/src/kernel/types/browsers/playwright/executor_delete_params.py b/src/kernel/types/browsers/playwright/executor_delete_params.py
new file mode 100644
index 00000000..e553f435
--- /dev/null
+++ b/src/kernel/types/browsers/playwright/executor_delete_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ExecutorDeleteParams"]
+
+
+class ExecutorDeleteParams(TypedDict, total=False):
+ id_or_name: Required[str]
+
+ close_tab: bool
+ """Close the executor's tab. Defaults to true."""
diff --git a/src/kernel/types/browsers/playwright/executor_list.py b/src/kernel/types/browsers/playwright/executor_list.py
new file mode 100644
index 00000000..c3e89c1f
--- /dev/null
+++ b/src/kernel/types/browsers/playwright/executor_list.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .executor import Executor
+from ...._models import BaseModel
+
+__all__ = ["ExecutorList"]
+
+
+class ExecutorList(BaseModel):
+ """The default executor followed by the named executors"""
+
+ executors: List[Executor]
diff --git a/src/kernel/types/browsers/playwright_execute_params.py b/src/kernel/types/browsers/playwright_execute_params.py
index 948a74c1..85980a9d 100644
--- a/src/kernel/types/browsers/playwright_execute_params.py
+++ b/src/kernel/types/browsers/playwright_execute_params.py
@@ -17,5 +17,13 @@ class PlaywrightExecuteParams(TypedDict, total=False):
"await page.goto('https://example.com'); return await page.title();"
"""
+ executor: str
+ """Name of a Playwright executor.
+
+ Calls with the same name run in the same executor, one at a time; the first call
+ with a new name creates it. Calls on different executors run concurrently.
+ 'default' names the executor that runs calls without a name.
+ """
+
timeout_sec: int
"""Maximum execution time in seconds. Default is 60."""
diff --git a/src/kernel/types/browsers/playwright_execute_response.py b/src/kernel/types/browsers/playwright_execute_response.py
index d53080de..63080119 100644
--- a/src/kernel/types/browsers/playwright_execute_response.py
+++ b/src/kernel/types/browsers/playwright_execute_response.py
@@ -2,6 +2,7 @@
from typing import Optional
+from .tab import Tab
from ..._models import BaseModel
__all__ = ["PlaywrightExecuteResponse"]
@@ -24,3 +25,9 @@ class PlaywrightExecuteResponse(BaseModel):
stdout: Optional[str] = None
"""Standard output from the execution"""
+
+ tab: Optional[Tab] = None
+ """The tab 'page' was bound to for this call.
+
+ Absent if the call failed before binding a tab.
+ """
diff --git a/src/kernel/types/browsers/tab.py b/src/kernel/types/browsers/tab.py
new file mode 100644
index 00000000..9f53eeca
--- /dev/null
+++ b/src/kernel/types/browsers/tab.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+
+__all__ = ["Tab"]
+
+
+class Tab(BaseModel):
+ """The tab 'page' was bound to for this call.
+
+ Absent if the call failed before binding a tab.
+ """
+
+ created: bool
+ """Whether this call opened the tab.
+
+ For a named executor this happens on its first call and after its previous tab
+ was closed. For the default executor it happens only when the browser had no
+ open page.
+ """
+
+ target_id: str
+ """CDP page target ID of the tab"""
diff --git a/src/kernel/types/invocation_list_browsers_response.py b/src/kernel/types/invocation_list_browsers_response.py
index a88f33f9..6589ad92 100644
--- a/src/kernel/types/invocation_list_browsers_response.py
+++ b/src/kernel/types/invocation_list_browsers_response.py
@@ -32,7 +32,7 @@ class Browser(BaseModel):
memory: BrowserMemory
"""Memory allocated to the browser session."""
- region: Literal["us-east", "eu-west", "ap-southeast"]
+ region: Literal["us-east", "us-west", "eu-west", "ap-southeast"]
"""Geographic region of the browser session. Fixed once the session is created."""
session_id: str
diff --git a/src/kernel/types/provider_target_param.py b/src/kernel/types/provider_target_param.py
index 7c3c314f..4251880f 100644
--- a/src/kernel/types/provider_target_param.py
+++ b/src/kernel/types/provider_target_param.py
@@ -99,7 +99,10 @@ class SearchExaTargetOptionsContents(TypedDict, total=False):
"""
highlights: bool
- """Return query-relevant provider excerpts."""
+ """Return query-relevant provider excerpts.
+
+ Defaults to true for Exa search requests; set false to disable.
+ """
text: bool
"""Return provider page text."""
@@ -166,7 +169,10 @@ class SearchPerplexityTargetOptions(TypedDict, total=False):
"""
search_context_size: Literal["low", "medium", "high"]
- """Provider context size supported by the selected model."""
+ """Amount of page content Perplexity extracts per result.
+
+ Defaults to medium unless max_tokens or max_tokens_per_page is set.
+ """
search_language_filter: SequenceNotStr[str]
"""ISO 639-1 language codes, max 20."""
diff --git a/src/kernel/types/vaults/kernel_card_state.py b/src/kernel/types/vaults/kernel_card_state.py
index 526eb4b3..6983c617 100644
--- a/src/kernel/types/vaults/kernel_card_state.py
+++ b/src/kernel/types/vaults/kernel_card_state.py
@@ -33,7 +33,7 @@ def __getattr__(self, attr: str) -> str: ...
class KernelCardState(BaseModel):
"""
- A ready Kernel card retains its encrypted network token and one-time code for the fill operation until the item's expires_at. Fill and submit checkout before then. Visa cards can be enrolled, but Visa purchases are not yet supported and authorize returns 400; supported Mastercard purchases need no cardholder approval. masks.last4 is the enrolled card's last four digits; masks.token_last4 is the network token's last four digits shown to the merchant. Kernel cards do not expose aliases or support egress substitution. Kernel does not observe whether the merchant charged the card.
+ A ready Kernel card retains its encrypted network token and one-time code for the fill operation until the item's expires_at. Fill and submit checkout before then. Visa purchases require a spend_approval action before the code is issued; Mastercard purchases need no hosted approval. masks.last4 is the enrolled card's last four digits; masks.token_last4 is the network token's last four digits shown to the merchant. Kernel cards do not expose aliases or support egress substitution. Kernel does not observe whether the merchant charged the card.
"""
provider: Literal["kernel"]
@@ -41,12 +41,15 @@ class KernelCardState(BaseModel):
status: Literal[
"requested", "pending_authorization", "ready", "consumed", "expired", "declined", "recovery_required"
]
- """recovery_required means issuing the one-time code has an unresolved outcome.
-
- Kernel never issues another code for the item automatically, and the item cannot
- be deleted or replaced until the original attempt is reconciled with support.
- When status_reason says the provider refused retrieval before acceptance, no
- code was issued and a later read retries.
+ """
+ pending_authorization on a Visa purchase waits for the cardholder to approve it
+ through the spend_approval action; an unused link expires after 30 minutes.
+ recovery_required means approving the purchase or issuing the one-time code has
+ an unresolved outcome. Kernel never approves again or issues another code for
+ the item automatically, and the item cannot be deleted or replaced until the
+ original attempt is reconciled with support. When status_reason says the
+ provider refused retrieval before acceptance, no code was issued and a later
+ read retries. declined means the card network refused to issue a code.
"""
domains: Optional[List[str]] = None
diff --git a/src/kernel/types/vaults/kernel_card_vault_item_spec.py b/src/kernel/types/vaults/kernel_card_vault_item_spec.py
index 65f75f8d..85d2d13d 100644
--- a/src/kernel/types/vaults/kernel_card_vault_item_spec.py
+++ b/src/kernel/types/vaults/kernel_card_vault_item_spec.py
@@ -1,5 +1,6 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from typing import Optional
from typing_extensions import Literal
from ..._models import BaseModel
@@ -10,7 +11,7 @@
class KernelCardVaultItemSpec(BaseModel):
"""One live purchase with a Kernel-enrolled card.
- Authorization obtains an agentic network token number, expiry and one-time 3-digit code. They are stored encrypted for the fill operation, which types them only on merchant_url's origin; the merchant's own checkout submits the payment. The one-time code is valid until the item's expires_at; fill and submit checkout before then. Visa cards can be enrolled, but Visa purchases are not yet supported: authorize returns 400. Supported Mastercard purchases need no cardholder approval. Card updates are not supported; delete and create a new item instead.
+ Authorization obtains an agentic network token number, expiry and one-time 3-digit code. They are stored encrypted for the fill operation, which types them only on merchant_url's origin; the merchant's own checkout submits the payment. The one-time code is valid until the item's expires_at; fill and submit checkout before then. Mastercard purchases need no cardholder approval. A Visa purchase returns a spend_approval action: the cardholder approves it with a Visa passkey, and Kernel registers a Visa intent for one transaction up to the amount before issuing the code. Card updates are not supported; delete and create a new item instead.
"""
amount: int
@@ -35,3 +36,10 @@ class KernelCardVaultItemSpec(BaseModel):
wallet: str
"""Key of the Kernel wallet item whose enrolled card pays."""
+
+ merchant_country: Optional[str] = None
+ """The merchant's ISO 3166-1 alpha-2 country code.
+
+ Required for Visa cards, whose one-time code is issued for the merchant's
+ country.
+ """
diff --git a/src/kernel/types/vaults/kernel_card_vault_item_spec_param.py b/src/kernel/types/vaults/kernel_card_vault_item_spec_param.py
index c625dfac..096e8c7b 100644
--- a/src/kernel/types/vaults/kernel_card_vault_item_spec_param.py
+++ b/src/kernel/types/vaults/kernel_card_vault_item_spec_param.py
@@ -10,7 +10,7 @@
class KernelCardVaultItemSpecParam(TypedDict, total=False):
"""One live purchase with a Kernel-enrolled card.
- Authorization obtains an agentic network token number, expiry and one-time 3-digit code. They are stored encrypted for the fill operation, which types them only on merchant_url's origin; the merchant's own checkout submits the payment. The one-time code is valid until the item's expires_at; fill and submit checkout before then. Visa cards can be enrolled, but Visa purchases are not yet supported: authorize returns 400. Supported Mastercard purchases need no cardholder approval. Card updates are not supported; delete and create a new item instead.
+ Authorization obtains an agentic network token number, expiry and one-time 3-digit code. They are stored encrypted for the fill operation, which types them only on merchant_url's origin; the merchant's own checkout submits the payment. The one-time code is valid until the item's expires_at; fill and submit checkout before then. Mastercard purchases need no cardholder approval. A Visa purchase returns a spend_approval action: the cardholder approves it with a Visa passkey, and Kernel registers a Visa intent for one transaction up to the amount before issuing the code. Card updates are not supported; delete and create a new item instead.
"""
amount: Required[int]
@@ -35,3 +35,10 @@ class KernelCardVaultItemSpecParam(TypedDict, total=False):
wallet: Required[str]
"""Key of the Kernel wallet item whose enrolled card pays."""
+
+ merchant_country: str
+ """The merchant's ISO 3166-1 alpha-2 country code.
+
+ Required for Visa cards, whose one-time code is issued for the merchant's
+ country.
+ """
diff --git a/src/kernel/types/vaults/kernel_wallet_state.py b/src/kernel/types/vaults/kernel_wallet_state.py
index 4051c709..a35239d7 100644
--- a/src/kernel/types/vaults/kernel_wallet_state.py
+++ b/src/kernel/types/vaults/kernel_wallet_state.py
@@ -14,10 +14,11 @@ class KernelWalletState(BaseModel):
status: Literal["pending_authorization", "connected", "reconnect_required", "degraded"]
"""pending_authorization asks the cardholder to use the card_enrollment action.
- connected is ready for supported purchases. reconnect_required asks the
- cardholder to use a new card_enrollment action after an uncertain enrollment was
- safely removed. degraded means the enrollment outcome is unknown and the wallet
- must be deleted before adding another card.
+ connected means the card is stored; it can pay once payment_methods reports
+ capabilities.single_use_card.eligible. reconnect_required asks the cardholder to
+ use a new card_enrollment action after an uncertain enrollment was safely
+ removed. degraded means the enrollment outcome is unknown and the wallet must be
+ deleted before adding another card.
"""
status_reason: Optional[str] = None
diff --git a/src/kernel/types/vaults/kernel_wallet_vault_item_spec.py b/src/kernel/types/vaults/kernel_wallet_vault_item_spec.py
index 70b95ea4..728d54f2 100644
--- a/src/kernel/types/vaults/kernel_wallet_vault_item_spec.py
+++ b/src/kernel/types/vaults/kernel_wallet_vault_item_spec.py
@@ -8,8 +8,9 @@
class KernelWalletVaultItemSpec(BaseModel):
- """
- One card Kernel enrolls for Visa or Mastercard agentic network tokens using Kernel-managed credentials. Creation returns a card_enrollment action: the cardholder enters the card and their email on a Kernel-hosted page, then Kernel enrolls the securely stored card. The card number never reaches Kernel. The connected wallet's payment_methods expansion lists the enrolled card. Visa cards can be enrolled, but Visa purchases are not yet supported: authorize returns 400.
+ """One card stored with Kernel-managed credentials.
+
+ Creation returns a card_enrollment action: the cardholder enters the card and their email on a Kernel-hosted page, and the wallet connects once the card is stored. Kernel then enrolls it for an agentic network token when the issuer supports it. The card number never reaches Kernel. The connected wallet's payment_methods expansion lists the card; capabilities.single_use_card.eligible is false, with a network_token_* reason, until the card has a network token, and authorize returns 400 for such a card. Visa purchases require the cardholder to complete a spend_approval action before Kernel issues a one-time code; Mastercard purchases need no hosted approval.
"""
provider: Literal["kernel"]
diff --git a/src/kernel/types/vaults/kernel_wallet_vault_item_spec_param.py b/src/kernel/types/vaults/kernel_wallet_vault_item_spec_param.py
index df1337c5..c29066ce 100644
--- a/src/kernel/types/vaults/kernel_wallet_vault_item_spec_param.py
+++ b/src/kernel/types/vaults/kernel_wallet_vault_item_spec_param.py
@@ -8,8 +8,9 @@
class KernelWalletVaultItemSpecParam(TypedDict, total=False):
- """
- One card Kernel enrolls for Visa or Mastercard agentic network tokens using Kernel-managed credentials. Creation returns a card_enrollment action: the cardholder enters the card and their email on a Kernel-hosted page, then Kernel enrolls the securely stored card. The card number never reaches Kernel. The connected wallet's payment_methods expansion lists the enrolled card. Visa cards can be enrolled, but Visa purchases are not yet supported: authorize returns 400.
+ """One card stored with Kernel-managed credentials.
+
+ Creation returns a card_enrollment action: the cardholder enters the card and their email on a Kernel-hosted page, and the wallet connects once the card is stored. Kernel then enrolls it for an agentic network token when the issuer supports it. The card number never reaches Kernel. The connected wallet's payment_methods expansion lists the card; capabilities.single_use_card.eligible is false, with a network_token_* reason, until the card has a network token, and authorize returns 400 for such a card. Visa purchases require the cardholder to complete a spend_approval action before Kernel issues a one-time code; Mastercard purchases need no hosted approval.
"""
provider: Required[Literal["kernel"]]
diff --git a/tests/api_resources/browsers/playwright/__init__.py b/tests/api_resources/browsers/playwright/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/browsers/playwright/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/browsers/playwright/test_executors.py b/tests/api_resources/browsers/playwright/test_executors.py
new file mode 100644
index 00000000..4b3b564e
--- /dev/null
+++ b/tests/api_resources/browsers/playwright/test_executors.py
@@ -0,0 +1,232 @@
+# 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 pytest
+
+from kernel import Kernel, AsyncKernel
+from tests.utils import assert_matches_type
+from kernel.types.browsers.playwright import ExecutorList
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestExecutors:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ executor = client.browsers.playwright.executors.list(
+ "htzv5orfit78e1m2biiifpbv",
+ )
+ assert_matches_type(ExecutorList, executor, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.browsers.playwright.executors.with_raw_response.list(
+ "htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ executor = response.parse()
+ assert_matches_type(ExecutorList, executor, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.browsers.playwright.executors.with_streaming_response.list(
+ "htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ executor = response.parse()
+ assert_matches_type(ExecutorList, executor, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_list(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.playwright.executors.with_raw_response.list(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: Kernel) -> None:
+ executor = client.browsers.playwright.executors.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+ assert executor is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete_with_all_params(self, client: Kernel) -> None:
+ executor = client.browsers.playwright.executors.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ close_tab=True,
+ )
+ assert executor is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Kernel) -> None:
+ response = client.browsers.playwright.executors.with_raw_response.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ executor = response.parse()
+ assert executor is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Kernel) -> None:
+ with client.browsers.playwright.executors.with_streaming_response.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ executor = response.parse()
+ assert executor is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_delete(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.browsers.playwright.executors.with_raw_response.delete(
+ name="checkout",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"):
+ client.browsers.playwright.executors.with_raw_response.delete(
+ name="",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+
+class TestAsyncExecutors:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncKernel) -> None:
+ executor = await async_client.browsers.playwright.executors.list(
+ "htzv5orfit78e1m2biiifpbv",
+ )
+ assert_matches_type(ExecutorList, executor, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncKernel) -> None:
+ response = await async_client.browsers.playwright.executors.with_raw_response.list(
+ "htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ executor = await response.parse()
+ assert_matches_type(ExecutorList, executor, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
+ async with async_client.browsers.playwright.executors.with_streaming_response.list(
+ "htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ executor = await response.parse()
+ assert_matches_type(ExecutorList, executor, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.playwright.executors.with_raw_response.list(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncKernel) -> None:
+ executor = await async_client.browsers.playwright.executors.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+ assert executor is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete_with_all_params(self, async_client: AsyncKernel) -> None:
+ executor = await async_client.browsers.playwright.executors.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ close_tab=True,
+ )
+ assert executor is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncKernel) -> None:
+ response = await async_client.browsers.playwright.executors.with_raw_response.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ executor = await response.parse()
+ assert executor is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncKernel) -> None:
+ async with async_client.browsers.playwright.executors.with_streaming_response.delete(
+ name="checkout",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ executor = await response.parse()
+ assert executor is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ await async_client.browsers.playwright.executors.with_raw_response.delete(
+ name="checkout",
+ id_or_name="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"):
+ await async_client.browsers.playwright.executors.with_raw_response.delete(
+ name="",
+ id_or_name="htzv5orfit78e1m2biiifpbv",
+ )
diff --git a/tests/api_resources/browsers/test_playwright.py b/tests/api_resources/browsers/test_playwright.py
index 5adc553f..3a2c457f 100644
--- a/tests/api_resources/browsers/test_playwright.py
+++ b/tests/api_resources/browsers/test_playwright.py
@@ -32,6 +32,7 @@ def test_method_execute_with_all_params(self, client: Kernel) -> None:
playwright = client.browsers.playwright.execute(
id_or_name="htzv5orfit78e1m2biiifpbv",
code="code",
+ executor="checkout",
timeout_sec=1,
)
assert_matches_type(PlaywrightExecuteResponse, playwright, path=["response"])
@@ -94,6 +95,7 @@ async def test_method_execute_with_all_params(self, async_client: AsyncKernel) -
playwright = await async_client.browsers.playwright.execute(
id_or_name="htzv5orfit78e1m2biiifpbv",
code="code",
+ executor="checkout",
timeout_sec=1,
)
assert_matches_type(PlaywrightExecuteResponse, playwright, path=["response"])
diff --git a/tests/api_resources/test_browser_pools.py b/tests/api_resources/test_browser_pools.py
index cd0c1b98..863c39c2 100644
--- a/tests/api_resources/test_browser_pools.py
+++ b/tests/api_resources/test_browser_pools.py
@@ -47,6 +47,7 @@ def test_method_create_with_all_params(self, client: Kernel) -> None:
memory="8GiB",
name="my-pool",
network={
+ "allowed_hosts": ["example.com", "*.example.com"],
"private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
"proxy_routes": [
{
@@ -199,6 +200,7 @@ def test_method_update_with_all_params(self, client: Kernel) -> None:
memory="8GiB",
name="my-pool",
network={
+ "allowed_hosts": ["example.com", "*.example.com"],
"private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
"proxy_routes": [
{
@@ -601,6 +603,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) ->
memory="8GiB",
name="my-pool",
network={
+ "allowed_hosts": ["example.com", "*.example.com"],
"private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
"proxy_routes": [
{
@@ -753,6 +756,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) ->
memory="8GiB",
name="my-pool",
network={
+ "allowed_hosts": ["example.com", "*.example.com"],
"private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
"proxy_routes": [
{
diff --git a/tests/api_resources/test_browsers.py b/tests/api_resources/test_browsers.py
index a9f0c46b..92f2149d 100644
--- a/tests/api_resources/test_browsers.py
+++ b/tests/api_resources/test_browsers.py
@@ -49,6 +49,7 @@ def test_method_create_with_all_params(self, client: Kernel) -> None:
memory="8GiB",
name="checkout-flow-1",
network={
+ "allowed_hosts": ["example.com", "*.example.com"],
"private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
"proxy_routes": [
{
@@ -593,6 +594,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) ->
memory="8GiB",
name="checkout-flow-1",
network={
+ "allowed_hosts": ["example.com", "*.example.com"],
"private_hosts": ["*.example.ts.net", "100.64.0.0/10"],
"proxy_routes": [
{