Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.29.0"
".": "0.30.0"
}
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 2 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ from kernel.types import (
InvocationUpdateResponse,
InvocationListResponse,
InvocationFollowResponse,
InvocationListBrowsersResponse,
)
```

Expand All @@ -70,6 +71,7 @@ Methods:
- <code title="get /invocations">client.invocations.<a href="./src/kernel/resources/invocations.py">list</a>(\*\*<a href="src/kernel/types/invocation_list_params.py">params</a>) -> <a href="./src/kernel/types/invocation_list_response.py">SyncOffsetPagination[InvocationListResponse]</a></code>
- <code title="delete /invocations/{id}/browsers">client.invocations.<a href="./src/kernel/resources/invocations.py">delete_browsers</a>(id) -> None</code>
- <code title="get /invocations/{id}/events">client.invocations.<a href="./src/kernel/resources/invocations.py">follow</a>(id, \*\*<a href="src/kernel/types/invocation_follow_params.py">params</a>) -> <a href="./src/kernel/types/invocation_follow_response.py">InvocationFollowResponse</a></code>
- <code title="get /invocations/{id}/browsers">client.invocations.<a href="./src/kernel/resources/invocations.py">list_browsers</a>(id) -> <a href="./src/kernel/types/invocation_list_browsers_response.py">InvocationListBrowsersResponse</a></code>

# Browsers

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/kernel/_version.py
Original file line number Diff line number Diff line change
@@ -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
79 changes: 79 additions & 0 deletions src/kernel/resources/invocations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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,
)
1 change: 1 addition & 0 deletions src/kernel/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
68 changes: 68 additions & 0 deletions src/kernel/types/invocation_list_browsers_response.py
Original file line number Diff line number Diff line change
@@ -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]
85 changes: 85 additions & 0 deletions tests/api_resources/test_invocations.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
InvocationCreateResponse,
InvocationUpdateResponse,
InvocationRetrieveResponse,
InvocationListBrowsersResponse,
)
from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
"",
)