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.19.2"
".": "0.20.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 66
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-d611cf8b0301a07123eab0e92498bea5ad69c5292b28aca1016c362cca0a0564.yml
openapi_spec_hash: 6d30f4ad9d61a7da8a75d543cf3d3d75
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-2af1b468584cb44aa9babbbfb82bff4055614fbb5c815084a6b7dacc1cf1a822.yml
openapi_spec_hash: 891affa2849341ea01d62011125f7edc
config_hash: 9421eb86b7f3f4b274f123279da3858e
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.20.0 (2025-11-19)

Full Changelog: [v0.19.2...v0.20.0](https://github.com/onkernel/kernel-python-sdk/compare/v0.19.2...v0.20.0)

### Features

* Add pagination to list browsers method and allow it to include deleted browsers when `include_deleted = true` ([0bf4d45](https://github.com/onkernel/kernel-python-sdk/commit/0bf4d4546171f7bc2fad9d225b7fcf2be14a6a71))

## 0.19.2 (2025-11-17)

Full Changelog: [v0.19.1...v0.19.2](https://github.com/onkernel/kernel-python-sdk/compare/v0.19.1...v0.19.2)
Expand Down
2 changes: 1 addition & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Methods:

- <code title="post /browsers">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">create</a>(\*\*<a href="src/kernel/types/browser_create_params.py">params</a>) -> <a href="./src/kernel/types/browser_create_response.py">BrowserCreateResponse</a></code>
- <code title="get /browsers/{id}">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">retrieve</a>(id) -> <a href="./src/kernel/types/browser_retrieve_response.py">BrowserRetrieveResponse</a></code>
- <code title="get /browsers">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">list</a>() -> <a href="./src/kernel/types/browser_list_response.py">BrowserListResponse</a></code>
- <code title="get /browsers">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">list</a>(\*\*<a href="src/kernel/types/browser_list_params.py">params</a>) -> <a href="./src/kernel/types/browser_list_response.py">SyncOffsetPagination[BrowserListResponse]</a></code>
- <code title="delete /browsers">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">delete</a>(\*\*<a href="src/kernel/types/browser_delete_params.py">params</a>) -> None</code>
- <code title="delete /browsers/{id}">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">delete_by_id</a>(id) -> None</code>
- <code title="post /browsers/{id}/extensions">client.browsers.<a href="./src/kernel/resources/browsers/browsers.py">load_extensions</a>(id, \*\*<a href="src/kernel/types/browser_load_extensions_params.py">params</a>) -> None</code>
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.19.2"
version = "0.20.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.19.2" # x-release-please-version
__version__ = "0.20.0" # x-release-please-version
102 changes: 89 additions & 13 deletions src/kernel/resources/browsers/browsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
FsResourceWithStreamingResponse,
AsyncFsResourceWithStreamingResponse,
)
from ...types import browser_create_params, browser_delete_params, browser_load_extensions_params
from ...types import (
browser_list_params,
browser_create_params,
browser_delete_params,
browser_load_extensions_params,
)
from .process import (
ProcessResource,
AsyncProcessResource,
Expand Down Expand Up @@ -65,7 +70,8 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
from ..._base_client import make_request_options
from ...pagination import SyncOffsetPagination, AsyncOffsetPagination
from ..._base_client import AsyncPaginator, make_request_options
from ...types.browser_list_response import BrowserListResponse
from ...types.browser_create_response import BrowserCreateResponse
from ...types.browser_persistence_param import BrowserPersistenceParam
Expand Down Expand Up @@ -247,20 +253,55 @@ def retrieve(
def list(
self,
*,
include_deleted: bool | Omit = omit,
limit: int | Omit = omit,
offset: 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.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BrowserListResponse:
"""List active browser sessions"""
return self._get(
) -> SyncOffsetPagination[BrowserListResponse]:
"""List all browser sessions with pagination support.

Use include_deleted=true to
include soft-deleted sessions in the results.

Args:
include_deleted: When true, includes soft-deleted browser sessions in the results alongside
active sessions.

limit: Maximum number of results to return. Defaults to 20, maximum 100.

offset: Number of results to skip. Defaults to 0.

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
"""
return self._get_api_list(
"/browsers",
page=SyncOffsetPagination[BrowserListResponse],
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
query=maybe_transform(
{
"include_deleted": include_deleted,
"limit": limit,
"offset": offset,
},
browser_list_params.BrowserListParams,
),
),
cast_to=BrowserListResponse,
model=BrowserListResponse,
)

def delete(
Expand Down Expand Up @@ -552,23 +593,58 @@ async def retrieve(
cast_to=BrowserRetrieveResponse,
)

async def list(
def list(
self,
*,
include_deleted: bool | Omit = omit,
limit: int | Omit = omit,
offset: 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.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BrowserListResponse:
"""List active browser sessions"""
return await self._get(
) -> AsyncPaginator[BrowserListResponse, AsyncOffsetPagination[BrowserListResponse]]:
"""List all browser sessions with pagination support.

Use include_deleted=true to
include soft-deleted sessions in the results.

Args:
include_deleted: When true, includes soft-deleted browser sessions in the results alongside
active sessions.

limit: Maximum number of results to return. Defaults to 20, maximum 100.

offset: Number of results to skip. Defaults to 0.

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
"""
return self._get_api_list(
"/browsers",
page=AsyncOffsetPagination[BrowserListResponse],
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
query=maybe_transform(
{
"include_deleted": include_deleted,
"limit": limit,
"offset": offset,
},
browser_list_params.BrowserListParams,
),
),
cast_to=BrowserListResponse,
model=BrowserListResponse,
)

async def delete(
Expand Down
1 change: 1 addition & 0 deletions src/kernel/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .profile import Profile as Profile
from .app_list_params import AppListParams as AppListParams
from .app_list_response import AppListResponse as AppListResponse
from .browser_list_params import BrowserListParams as BrowserListParams
from .browser_persistence import BrowserPersistence as BrowserPersistence
from .proxy_create_params import ProxyCreateParams as ProxyCreateParams
from .proxy_list_response import ProxyListResponse as ProxyListResponse
Expand Down
3 changes: 3 additions & 0 deletions src/kernel/types/browser_create_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class BrowserCreateResponse(BaseModel):
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."""

Expand Down
21 changes: 21 additions & 0 deletions src/kernel/types/browser_list_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import TypedDict

__all__ = ["BrowserListParams"]


class BrowserListParams(TypedDict, total=False):
include_deleted: bool
"""
When true, includes soft-deleted browser sessions in the results alongside
active sessions.
"""

limit: int
"""Maximum number of results to return. Defaults to 20, maximum 100."""

offset: int
"""Number of results to skip. Defaults to 0."""
17 changes: 8 additions & 9 deletions src/kernel/types/browser_list_response.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from typing import List, Optional
from typing import Optional
from datetime import datetime
from typing_extensions import TypeAlias

from .profile import Profile
from .._models import BaseModel
from .browser_persistence import BrowserPersistence

__all__ = ["BrowserListResponse", "BrowserListResponseItem", "BrowserListResponseItemViewport"]
__all__ = ["BrowserListResponse", "Viewport"]


class BrowserListResponseItemViewport(BaseModel):
class Viewport(BaseModel):
height: int
"""Browser window height in pixels."""

Expand All @@ -25,7 +24,7 @@ class BrowserListResponseItemViewport(BaseModel):
"""


class BrowserListResponseItem(BaseModel):
class BrowserListResponse(BaseModel):
cdp_ws_url: str
"""Websocket URL for Chrome DevTools Protocol connections to the browser session"""

Expand All @@ -50,6 +49,9 @@ class BrowserListResponseItem(BaseModel):
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."""

Expand All @@ -62,7 +64,7 @@ class BrowserListResponseItem(BaseModel):
proxy_id: Optional[str] = None
"""ID of the proxy associated with this browser session, if any."""

viewport: Optional[BrowserListResponseItemViewport] = None
viewport: Optional[Viewport] = None
"""Initial browser window size in pixels with optional refresh rate.

If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport
Expand All @@ -73,6 +75,3 @@ class BrowserListResponseItem(BaseModel):
configuration exactly. Note: Higher resolutions may affect the responsiveness of
live view browser
"""


BrowserListResponse: TypeAlias = List[BrowserListResponseItem]
3 changes: 3 additions & 0 deletions src/kernel/types/browser_retrieve_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class BrowserRetrieveResponse(BaseModel):
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."""

Expand Down
33 changes: 27 additions & 6 deletions tests/api_resources/test_browsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
BrowserCreateResponse,
BrowserRetrieveResponse,
)
from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination

base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")

Expand Down Expand Up @@ -125,7 +126,17 @@ def test_path_params_retrieve(self, client: Kernel) -> None:
@parametrize
def test_method_list(self, client: Kernel) -> None:
browser = client.browsers.list()
assert_matches_type(BrowserListResponse, browser, path=["response"])
assert_matches_type(SyncOffsetPagination[BrowserListResponse], browser, path=["response"])

@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_method_list_with_all_params(self, client: Kernel) -> None:
browser = client.browsers.list(
include_deleted=True,
limit=1,
offset=0,
)
assert_matches_type(SyncOffsetPagination[BrowserListResponse], browser, path=["response"])

@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
Expand All @@ -135,7 +146,7 @@ def test_raw_response_list(self, client: Kernel) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
browser = response.parse()
assert_matches_type(BrowserListResponse, browser, path=["response"])
assert_matches_type(SyncOffsetPagination[BrowserListResponse], browser, path=["response"])

@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
Expand All @@ -145,7 +156,7 @@ def test_streaming_response_list(self, client: Kernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"

browser = response.parse()
assert_matches_type(BrowserListResponse, browser, path=["response"])
assert_matches_type(SyncOffsetPagination[BrowserListResponse], browser, path=["response"])

assert cast(Any, response.is_closed) is True

Expand Down Expand Up @@ -401,7 +412,17 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_list(self, async_client: AsyncKernel) -> None:
browser = await async_client.browsers.list()
assert_matches_type(BrowserListResponse, browser, path=["response"])
assert_matches_type(AsyncOffsetPagination[BrowserListResponse], browser, path=["response"])

@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None:
browser = await async_client.browsers.list(
include_deleted=True,
limit=1,
offset=0,
)
assert_matches_type(AsyncOffsetPagination[BrowserListResponse], browser, path=["response"])

@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
Expand All @@ -411,7 +432,7 @@ async def test_raw_response_list(self, async_client: AsyncKernel) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
browser = await response.parse()
assert_matches_type(BrowserListResponse, browser, path=["response"])
assert_matches_type(AsyncOffsetPagination[BrowserListResponse], browser, path=["response"])

@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
Expand All @@ -421,7 +442,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"

browser = await response.parse()
assert_matches_type(BrowserListResponse, browser, path=["response"])
assert_matches_type(AsyncOffsetPagination[BrowserListResponse], browser, path=["response"])

assert cast(Any, response.is_closed) is True

Expand Down