diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index b8dda9b..554e34b 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.29.0"
+ ".": "0.30.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 470e498..710d8bd 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 97
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-46c8320dcd9f8fc596f469ef0dd1aafaca591ab36cf2a6f8a7297dc9136bdc71.yml
-openapi_spec_hash: 1be1e6589cd94c581b241720e01a65bc
-config_hash: b470456b217bb9502f5212311d395a6f
+configured_endpoints: 98
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-ccbe854895eb34a9562e33979f5f43cd6ad1f529d5924ee56e56f0c94dcf0454.yml
+openapi_spec_hash: 2fa4ecbe742fc46fdde481188c1d885e
+config_hash: dd218aae3f852dff79e77febc2077b8e
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f736cb..45e5270 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 0.30.0 (2026-02-03)
+
+Full Changelog: [v0.29.0...v0.30.0](https://github.com/kernel/kernel-python-sdk/compare/v0.29.0...v0.30.0)
+
+### Features
+
+* Neil/kernel 872 templates v3 ([383d071](https://github.com/kernel/kernel-python-sdk/commit/383d071b2dba88ec884c34ea8453b3c0e9c4a969))
+
## 0.29.0 (2026-01-30)
Full Changelog: [v0.28.0...v0.29.0](https://github.com/kernel/kernel-python-sdk/compare/v0.28.0...v0.29.0)
diff --git a/api.md b/api.md
index e951a78..8211665 100644
--- a/api.md
+++ b/api.md
@@ -59,6 +59,7 @@ from kernel.types import (
InvocationUpdateResponse,
InvocationListResponse,
InvocationFollowResponse,
+ InvocationListBrowsersResponse,
)
```
@@ -70,6 +71,7 @@ Methods:
- client.invocations.list(\*\*params) -> SyncOffsetPagination[InvocationListResponse]
- client.invocations.delete_browsers(id) -> None
- client.invocations.follow(id, \*\*params) -> InvocationFollowResponse
+- client.invocations.list_browsers(id) -> InvocationListBrowsersResponse
# Browsers
diff --git a/pyproject.toml b/pyproject.toml
index 6643370..16affc3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "kernel"
-version = "0.29.0"
+version = "0.30.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 d24a1a6..e08b152 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.29.0" # x-release-please-version
+__version__ = "0.30.0" # x-release-please-version
diff --git a/src/kernel/resources/invocations.py b/src/kernel/resources/invocations.py
index 3b812d4..3194026 100644
--- a/src/kernel/resources/invocations.py
+++ b/src/kernel/resources/invocations.py
@@ -26,6 +26,7 @@
from ..types.invocation_follow_response import InvocationFollowResponse
from ..types.invocation_update_response import InvocationUpdateResponse
from ..types.invocation_retrieve_response import InvocationRetrieveResponse
+from ..types.invocation_list_browsers_response import InvocationListBrowsersResponse
__all__ = ["InvocationsResource", "AsyncInvocationsResource"]
@@ -347,6 +348,39 @@ def follow(
stream_cls=Stream[InvocationFollowResponse],
)
+ def list_browsers(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> InvocationListBrowsersResponse:
+ """
+ Returns all active browser sessions created within the specified invocation.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ f"/invocations/{id}/browsers",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=InvocationListBrowsersResponse,
+ )
+
class AsyncInvocationsResource(AsyncAPIResource):
@cached_property
@@ -665,6 +699,39 @@ async def follow(
stream_cls=AsyncStream[InvocationFollowResponse],
)
+ async def list_browsers(
+ self,
+ id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> InvocationListBrowsersResponse:
+ """
+ Returns all active browser sessions created within the specified invocation.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ f"/invocations/{id}/browsers",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=InvocationListBrowsersResponse,
+ )
+
class InvocationsResourceWithRawResponse:
def __init__(self, invocations: InvocationsResource) -> None:
@@ -688,6 +755,9 @@ def __init__(self, invocations: InvocationsResource) -> None:
self.follow = to_raw_response_wrapper(
invocations.follow,
)
+ self.list_browsers = to_raw_response_wrapper(
+ invocations.list_browsers,
+ )
class AsyncInvocationsResourceWithRawResponse:
@@ -712,6 +782,9 @@ def __init__(self, invocations: AsyncInvocationsResource) -> None:
self.follow = async_to_raw_response_wrapper(
invocations.follow,
)
+ self.list_browsers = async_to_raw_response_wrapper(
+ invocations.list_browsers,
+ )
class InvocationsResourceWithStreamingResponse:
@@ -736,6 +809,9 @@ def __init__(self, invocations: InvocationsResource) -> None:
self.follow = to_streamed_response_wrapper(
invocations.follow,
)
+ self.list_browsers = to_streamed_response_wrapper(
+ invocations.list_browsers,
+ )
class AsyncInvocationsResourceWithStreamingResponse:
@@ -760,3 +836,6 @@ def __init__(self, invocations: AsyncInvocationsResource) -> None:
self.follow = async_to_streamed_response_wrapper(
invocations.follow,
)
+ self.list_browsers = async_to_streamed_response_wrapper(
+ invocations.list_browsers,
+ )
diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py
index ef16ae2..7e3ea2d 100644
--- a/src/kernel/types/__init__.py
+++ b/src/kernel/types/__init__.py
@@ -74,6 +74,7 @@
from .credential_provider_create_params import CredentialProviderCreateParams as CredentialProviderCreateParams
from .credential_provider_list_response import CredentialProviderListResponse as CredentialProviderListResponse
from .credential_provider_update_params import CredentialProviderUpdateParams as CredentialProviderUpdateParams
+from .invocation_list_browsers_response import InvocationListBrowsersResponse as InvocationListBrowsersResponse
from .extension_download_from_chrome_store_params import (
ExtensionDownloadFromChromeStoreParams as ExtensionDownloadFromChromeStoreParams,
)
diff --git a/src/kernel/types/invocation_list_browsers_response.py b/src/kernel/types/invocation_list_browsers_response.py
new file mode 100644
index 0000000..4d41a29
--- /dev/null
+++ b/src/kernel/types/invocation_list_browsers_response.py
@@ -0,0 +1,68 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from .profile import Profile
+from .._models import BaseModel
+from .browser_persistence import BrowserPersistence
+from .shared.browser_viewport import BrowserViewport
+
+__all__ = ["InvocationListBrowsersResponse", "Browser"]
+
+
+class Browser(BaseModel):
+ cdp_ws_url: str
+ """Websocket URL for Chrome DevTools Protocol connections to the browser session"""
+
+ created_at: datetime
+ """When the browser session was created."""
+
+ headless: bool
+ """Whether the browser session is running in headless mode."""
+
+ session_id: str
+ """Unique identifier for the browser session"""
+
+ stealth: bool
+ """Whether the browser session is running in stealth mode."""
+
+ timeout_seconds: int
+ """The number of seconds of inactivity before the browser session is terminated."""
+
+ browser_live_view_url: Optional[str] = None
+ """Remote URL for live viewing the browser session.
+
+ Only available for non-headless browsers.
+ """
+
+ deleted_at: Optional[datetime] = None
+ """When the browser session was soft-deleted. Only present for deleted sessions."""
+
+ kiosk_mode: Optional[bool] = None
+ """Whether the browser session is running in kiosk mode."""
+
+ persistence: Optional[BrowserPersistence] = None
+ """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead."""
+
+ profile: Optional[Profile] = None
+ """Browser profile metadata."""
+
+ proxy_id: Optional[str] = None
+ """ID of the proxy associated with this browser session, if any."""
+
+ viewport: Optional[BrowserViewport] = None
+ """Initial browser window size in pixels with optional refresh rate.
+
+ If omitted, image defaults apply (1920x1080@25). Only specific viewport
+ configurations are supported. The server will reject unsupported combinations.
+ Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60 If refresh_rate is not
+ provided, it will be automatically determined from the width and height if they
+ match a supported configuration exactly. Note: Higher resolutions may affect the
+ responsiveness of live view browser
+ """
+
+
+class InvocationListBrowsersResponse(BaseModel):
+ browsers: List[Browser]
diff --git a/tests/api_resources/test_invocations.py b/tests/api_resources/test_invocations.py
index 40c0545..d870adc 100644
--- a/tests/api_resources/test_invocations.py
+++ b/tests/api_resources/test_invocations.py
@@ -14,6 +14,7 @@
InvocationCreateResponse,
InvocationUpdateResponse,
InvocationRetrieveResponse,
+ InvocationListBrowsersResponse,
)
from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
@@ -309,6 +310,48 @@ def test_path_params_follow(self, client: Kernel) -> None:
id="",
)
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list_browsers(self, client: Kernel) -> None:
+ invocation = client.invocations.list_browsers(
+ "id",
+ )
+ assert_matches_type(InvocationListBrowsersResponse, invocation, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_list_browsers(self, client: Kernel) -> None:
+ response = client.invocations.with_raw_response.list_browsers(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ invocation = response.parse()
+ assert_matches_type(InvocationListBrowsersResponse, invocation, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_list_browsers(self, client: Kernel) -> None:
+ with client.invocations.with_streaming_response.list_browsers(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ invocation = response.parse()
+ assert_matches_type(InvocationListBrowsersResponse, invocation, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_list_browsers(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.invocations.with_raw_response.list_browsers(
+ "",
+ )
+
class TestAsyncInvocations:
parametrize = pytest.mark.parametrize(
@@ -600,3 +643,45 @@ async def test_path_params_follow(self, async_client: AsyncKernel) -> None:
await async_client.invocations.with_raw_response.follow(
id="",
)
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list_browsers(self, async_client: AsyncKernel) -> None:
+ invocation = await async_client.invocations.list_browsers(
+ "id",
+ )
+ assert_matches_type(InvocationListBrowsersResponse, invocation, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_list_browsers(self, async_client: AsyncKernel) -> None:
+ response = await async_client.invocations.with_raw_response.list_browsers(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ invocation = await response.parse()
+ assert_matches_type(InvocationListBrowsersResponse, invocation, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_list_browsers(self, async_client: AsyncKernel) -> None:
+ async with async_client.invocations.with_streaming_response.list_browsers(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ invocation = await response.parse()
+ assert_matches_type(InvocationListBrowsersResponse, invocation, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_list_browsers(self, async_client: AsyncKernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.invocations.with_raw_response.list_browsers(
+ "",
+ )