Skip to content

Commit c9e1eb0

Browse files
authored
Merge pull request #110 from kernel/release-please--branches--main--changes--next
release: 0.60.0
2 parents 4c5dde5 + cecaff2 commit c9e1eb0

16 files changed

Lines changed: 177 additions & 39 deletions

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.59.0"
2+
".": "0.60.0"
33
}

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 117
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-1acd8f0b76ab00e36b53cc3ca90b72b2199f3388b3e307890adb464b87f9a2d8.yml
3-
openapi_spec_hash: 82003125c1c2c5d82d19270bafb4a6ca
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-3a1db7f11a92b28681929255ada59d2317ee4db98b5ff5aa6ce142a0664058b3.yml
3+
openapi_spec_hash: b3064eaa589ae2a84993686ad1a3ee43
44
config_hash: ede72e4ae65cc5a6d6927938b3455c46

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## 0.60.0 (2026-06-03)
4+
5+
Full Changelog: [v0.59.0...v0.60.0](https://github.com/kernel/kernel-python-sdk/compare/v0.59.0...v0.60.0)
6+
7+
### Features
8+
9+
* Add API-backed API key management endpoints ([55bd31e](https://github.com/kernel/kernel-python-sdk/commit/55bd31edfed74f3bc4b08f83e07448e2e53378b0))
10+
* **examples:** add browser-telemetry example ([4c29993](https://github.com/kernel/kernel-python-sdk/commit/4c29993cdda50fcde19ee7bf696a268cd8b0eb0b))
11+
* Fix browser pool update schema ([903fe13](https://github.com/kernel/kernel-python-sdk/commit/903fe13b84242acea9c27bc451c16fb5cd58e40c))
12+
* route browser telemetry directly to the VM by default ([cb50725](https://github.com/kernel/kernel-python-sdk/commit/cb5072516416627ffde7198900484c46dda5b9dc))
13+
14+
15+
### Bug Fixes
16+
17+
* **streaming:** don't dispatch empty SSE keepalive comment frames ([a0ee2b2](https://github.com/kernel/kernel-python-sdk/commit/a0ee2b2653c581839be11bb29be5b68166e80658))
18+
319
## 0.59.0 (2026-06-03)
420

521
Full Changelog: [v0.58.0...v0.59.0](https://github.com/kernel/kernel-python-sdk/compare/v0.58.0...v0.59.0)

examples/browser_telemetry.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Example: stream live browser telemetry events from a session."""
2+
3+
from kernel import Kernel
4+
5+
6+
def main() -> None:
7+
client = Kernel()
8+
9+
# Enable telemetry capture when creating the browser.
10+
browser = client.browsers.create(telemetry={"enabled": True})
11+
12+
try:
13+
# Telemetry is a default direct-to-VM routing subresource, so the stream
14+
# connects straight to the browser VM automatically.
15+
stream = client.browsers.telemetry.stream(browser.session_id)
16+
17+
# Make a few browser activity calls to generate events. The "api" telemetry
18+
# category emits an event per VM API call, so events arrive within ~1s.
19+
for _ in range(3):
20+
client.browsers.curl(browser.session_id, url="https://example.com", method="GET")
21+
22+
# Print a few events, then stop so we don't wait on the 15s keepalive.
23+
for count, message in enumerate(stream, start=1):
24+
print(message.seq, message.event.type)
25+
if count >= 3:
26+
break
27+
finally:
28+
client.browsers.delete_by_id(browser.session_id)
29+
30+
31+
if __name__ == "__main__":
32+
main()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "kernel"
3-
version = "0.59.0"
3+
version = "0.60.0"
44
description = "The official Python library for the kernel API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"

src/kernel/_streaming.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,12 @@ def decode(self, line: str) -> ServerSentEvent | None:
251251
# See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501
252252

253253
if not line:
254-
if not self._event and not self._data and not self._last_event_id and self._retry is None:
254+
# Whether to dispatch depends only on what was set in the *current* block. last_event_id
255+
# is sticky across events (per the SSE spec, it is intentionally not reset below), so it
256+
# must not be part of this check -- otherwise, once any event carries an id, every
257+
# subsequent comment-only block (e.g. a ``:\n\n`` keepalive) would dispatch an empty
258+
# event, which then fails to JSON-decode in the typed Stream wrapper.
259+
if not self._event and not self._data and self._retry is None:
255260
return None
256261

257262
sse = ServerSentEvent(

src/kernel/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
__title__ = "kernel"
4-
__version__ = "0.59.0" # x-release-please-version
4+
__version__ = "0.60.0" # x-release-please-version

src/kernel/lib/browser_routing/routing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class BrowserRoutingConfig:
4141
def browser_routing_config_from_env() -> BrowserRoutingConfig:
4242
raw = os.environ.get("KERNEL_BROWSER_ROUTING_SUBRESOURCES")
4343
if raw is None:
44-
return BrowserRoutingConfig(subresources=("curl",))
44+
return BrowserRoutingConfig(subresources=("curl", "telemetry"))
4545
if raw.strip() == "":
4646
return BrowserRoutingConfig()
4747

src/kernel/resources/api_keys.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
from typing import Optional
6+
from typing_extensions import Literal
67

78
import httpx
89

@@ -171,6 +172,9 @@ def list(
171172
*,
172173
limit: int | Omit = omit,
173174
offset: int | Omit = omit,
175+
query: str | Omit = omit,
176+
sort_by: Literal["created_at", "name", "expires_at"] | Omit = omit,
177+
sort_direction: Literal["asc", "desc"] | Omit = omit,
174178
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
175179
# The extra values given here take precedence over values defined on the client or passed to this method.
176180
extra_headers: Headers | None = None,
@@ -187,6 +191,13 @@ def list(
187191
188192
offset: Number of results to skip
189193
194+
query: Case-insensitive substring match against API key name, creator, and project. API
195+
key identifiers and masked keys match by exact value or prefix.
196+
197+
sort_by: Field to sort API keys by.
198+
199+
sort_direction: Sort direction for API keys.
200+
190201
extra_headers: Send extra headers
191202
192203
extra_query: Add additional query parameters to the request
@@ -207,6 +218,9 @@ def list(
207218
{
208219
"limit": limit,
209220
"offset": offset,
221+
"query": query,
222+
"sort_by": sort_by,
223+
"sort_direction": sort_direction,
210224
},
211225
api_key_list_params.APIKeyListParams,
212226
),
@@ -395,6 +409,9 @@ def list(
395409
*,
396410
limit: int | Omit = omit,
397411
offset: int | Omit = omit,
412+
query: str | Omit = omit,
413+
sort_by: Literal["created_at", "name", "expires_at"] | Omit = omit,
414+
sort_direction: Literal["asc", "desc"] | Omit = omit,
398415
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
399416
# The extra values given here take precedence over values defined on the client or passed to this method.
400417
extra_headers: Headers | None = None,
@@ -411,6 +428,13 @@ def list(
411428
412429
offset: Number of results to skip
413430
431+
query: Case-insensitive substring match against API key name, creator, and project. API
432+
key identifiers and masked keys match by exact value or prefix.
433+
434+
sort_by: Field to sort API keys by.
435+
436+
sort_direction: Sort direction for API keys.
437+
414438
extra_headers: Send extra headers
415439
416440
extra_query: Add additional query parameters to the request
@@ -431,6 +455,9 @@ def list(
431455
{
432456
"limit": limit,
433457
"offset": offset,
458+
"query": query,
459+
"sort_by": sort_by,
460+
"sort_direction": sort_direction,
434461
},
435462
api_key_list_params.APIKeyListParams,
436463
),

src/kernel/resources/browser_pools.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ def update(
206206
self,
207207
id_or_name: str,
208208
*,
209-
size: int,
210209
chrome_policy: Dict[str, object] | Omit = omit,
211210
discard_all_idle: bool | Omit = omit,
212211
extensions: Iterable[BrowserExtension] | Omit = omit,
@@ -216,6 +215,7 @@ def update(
216215
name: str | Omit = omit,
217216
profile: BrowserProfile | Omit = omit,
218217
proxy_id: str | Omit = omit,
218+
size: int | Omit = omit,
219219
start_url: str | Omit = omit,
220220
stealth: bool | Omit = omit,
221221
timeout_seconds: int | Omit = omit,
@@ -231,10 +231,6 @@ def update(
231231
Updates the configuration used to create browsers in the pool.
232232
233233
Args:
234-
size: Number of browsers to maintain in the pool. The maximum size is determined by
235-
your organization's pooled sessions limit (the sum of all pool sizes cannot
236-
exceed your limit).
237-
238234
chrome_policy: Custom Chrome enterprise policy overrides applied to all browsers in this pool.
239235
Keys are Chrome enterprise policy names; values must match their expected types.
240236
Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
@@ -261,6 +257,10 @@ def update(
261257
proxy_id: Optional proxy to associate to the browser session. Must reference a proxy
262258
belonging to the caller's org.
263259
260+
size: Number of browsers to maintain in the pool. The maximum size is determined by
261+
your organization's pooled sessions limit (the sum of all pool sizes cannot
262+
exceed your limit).
263+
264264
start_url: Optional URL to navigate to when a new browser is warmed into the pool.
265265
Best-effort: failures to navigate do not fail pool fill. Only applied to
266266
newly-warmed browsers; browsers reused via release/acquire keep whatever URL the
@@ -300,7 +300,6 @@ def update(
300300
path_template("/browser_pools/{id_or_name}", id_or_name=id_or_name),
301301
body=maybe_transform(
302302
{
303-
"size": size,
304303
"chrome_policy": chrome_policy,
305304
"discard_all_idle": discard_all_idle,
306305
"extensions": extensions,
@@ -310,6 +309,7 @@ def update(
310309
"name": name,
311310
"profile": profile,
312311
"proxy_id": proxy_id,
312+
"size": size,
313313
"start_url": start_url,
314314
"stealth": stealth,
315315
"timeout_seconds": timeout_seconds,
@@ -684,7 +684,6 @@ async def update(
684684
self,
685685
id_or_name: str,
686686
*,
687-
size: int,
688687
chrome_policy: Dict[str, object] | Omit = omit,
689688
discard_all_idle: bool | Omit = omit,
690689
extensions: Iterable[BrowserExtension] | Omit = omit,
@@ -694,6 +693,7 @@ async def update(
694693
name: str | Omit = omit,
695694
profile: BrowserProfile | Omit = omit,
696695
proxy_id: str | Omit = omit,
696+
size: int | Omit = omit,
697697
start_url: str | Omit = omit,
698698
stealth: bool | Omit = omit,
699699
timeout_seconds: int | Omit = omit,
@@ -709,10 +709,6 @@ async def update(
709709
Updates the configuration used to create browsers in the pool.
710710
711711
Args:
712-
size: Number of browsers to maintain in the pool. The maximum size is determined by
713-
your organization's pooled sessions limit (the sum of all pool sizes cannot
714-
exceed your limit).
715-
716712
chrome_policy: Custom Chrome enterprise policy overrides applied to all browsers in this pool.
717713
Keys are Chrome enterprise policy names; values must match their expected types.
718714
Blocked: kernel-managed policies (extensions, proxy, CDP/automation). See
@@ -739,6 +735,10 @@ async def update(
739735
proxy_id: Optional proxy to associate to the browser session. Must reference a proxy
740736
belonging to the caller's org.
741737
738+
size: Number of browsers to maintain in the pool. The maximum size is determined by
739+
your organization's pooled sessions limit (the sum of all pool sizes cannot
740+
exceed your limit).
741+
742742
start_url: Optional URL to navigate to when a new browser is warmed into the pool.
743743
Best-effort: failures to navigate do not fail pool fill. Only applied to
744744
newly-warmed browsers; browsers reused via release/acquire keep whatever URL the
@@ -778,7 +778,6 @@ async def update(
778778
path_template("/browser_pools/{id_or_name}", id_or_name=id_or_name),
779779
body=await async_maybe_transform(
780780
{
781-
"size": size,
782781
"chrome_policy": chrome_policy,
783782
"discard_all_idle": discard_all_idle,
784783
"extensions": extensions,
@@ -788,6 +787,7 @@ async def update(
788787
"name": name,
789788
"profile": profile,
790789
"proxy_id": proxy_id,
790+
"size": size,
791791
"start_url": start_url,
792792
"stealth": stealth,
793793
"timeout_seconds": timeout_seconds,

0 commit comments

Comments
 (0)