From 0d5a4c29be2e2bc0a216436cbda4bc8602784d02 Mon Sep 17 00:00:00 2001
From: "kernel-internal[bot]"
<260533166+kernel-internal[bot]@users.noreply.github.com>
Date: Thu, 13 Aug 2026 20:48:05 +0000
Subject: [PATCH 1/4] feat: Require a name on credential providers and backfill
unnamed rows
Stainless-Generated-From: 0b9c98f90d25c32e13ea19b634f735d1709c371d
---
src/kernel/resources/credential_providers.py | 12 ++++++++----
.../types/credential_provider_create_params.py | 5 ++++-
.../types/credential_provider_update_params.py | 5 ++++-
3 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/src/kernel/resources/credential_providers.py b/src/kernel/resources/credential_providers.py
index de079a8c..6a003f99 100644
--- a/src/kernel/resources/credential_providers.py
+++ b/src/kernel/resources/credential_providers.py
@@ -73,7 +73,8 @@ def create(
Args:
token: Service account token for the provider (e.g., 1Password service account token)
- name: Human-readable name for this provider instance (unique per org)
+ name: Human-readable name for this provider instance (unique per org). Surrounding
+ whitespace is trimmed and the trimmed value must be non-empty.
provider_type: Type of credential provider
@@ -163,7 +164,8 @@ def update(
enabled: Whether the provider is enabled for credential lookups
- name: Human-readable name for this provider instance
+ name: Human-readable name for this provider instance. Surrounding whitespace is
+ trimmed and the trimmed value must be non-empty.
priority: Priority order for credential lookups (lower numbers are checked first)
@@ -392,7 +394,8 @@ async def create(
Args:
token: Service account token for the provider (e.g., 1Password service account token)
- name: Human-readable name for this provider instance (unique per org)
+ name: Human-readable name for this provider instance (unique per org). Surrounding
+ whitespace is trimmed and the trimmed value must be non-empty.
provider_type: Type of credential provider
@@ -482,7 +485,8 @@ async def update(
enabled: Whether the provider is enabled for credential lookups
- name: Human-readable name for this provider instance
+ name: Human-readable name for this provider instance. Surrounding whitespace is
+ trimmed and the trimmed value must be non-empty.
priority: Priority order for credential lookups (lower numbers are checked first)
diff --git a/src/kernel/types/credential_provider_create_params.py b/src/kernel/types/credential_provider_create_params.py
index ea531c7d..7076ff72 100644
--- a/src/kernel/types/credential_provider_create_params.py
+++ b/src/kernel/types/credential_provider_create_params.py
@@ -12,7 +12,10 @@ class CredentialProviderCreateParams(TypedDict, total=False):
"""Service account token for the provider (e.g., 1Password service account token)"""
name: Required[str]
- """Human-readable name for this provider instance (unique per org)"""
+ """Human-readable name for this provider instance (unique per org).
+
+ Surrounding whitespace is trimmed and the trimmed value must be non-empty.
+ """
provider_type: Required[Literal["onepassword"]]
"""Type of credential provider"""
diff --git a/src/kernel/types/credential_provider_update_params.py b/src/kernel/types/credential_provider_update_params.py
index b2dda02b..272a7661 100644
--- a/src/kernel/types/credential_provider_update_params.py
+++ b/src/kernel/types/credential_provider_update_params.py
@@ -18,7 +18,10 @@ class CredentialProviderUpdateParams(TypedDict, total=False):
"""Whether the provider is enabled for credential lookups"""
name: str
- """Human-readable name for this provider instance"""
+ """Human-readable name for this provider instance.
+
+ Surrounding whitespace is trimmed and the trimmed value must be non-empty.
+ """
priority: int
"""Priority order for credential lookups (lower numbers are checked first)"""
From ccf4127272adc749f0da75b7e2458e4a9e183423 Mon Sep 17 00:00:00 2001
From: "kernel-internal[bot]"
<260533166+kernel-internal[bot]@users.noreply.github.com>
Date: Fri, 14 Aug 2026 17:49:41 +0000
Subject: [PATCH 2/4] feat: Expose configurable browser memory
Stainless-Generated-From: 32f5b5d0edf73007529398a1c89d0f9ef15e060a
---
api.md | 2 ++
src/kernel/resources/browsers/browsers.py | 10 ++++++++++
src/kernel/types/__init__.py | 2 ++
src/kernel/types/browser_create_params.py | 4 ++++
src/kernel/types/browser_create_response.py | 4 ++++
src/kernel/types/browser_list_response.py | 4 ++++
src/kernel/types/browser_memory.py | 7 +++++++
src/kernel/types/browser_memory_request.py | 7 +++++++
src/kernel/types/browser_pool_acquire_response.py | 4 ++++
src/kernel/types/browser_retrieve_response.py | 4 ++++
src/kernel/types/browser_update_response.py | 4 ++++
src/kernel/types/invocation_list_browsers_response.py | 4 ++++
tests/api_resources/test_browsers.py | 2 ++
tests/test_browser_routing.py | 1 +
14 files changed, 59 insertions(+)
create mode 100644 src/kernel/types/browser_memory.py
create mode 100644 src/kernel/types/browser_memory_request.py
diff --git a/api.md b/api.md
index d442a3d0..52d4e4c3 100644
--- a/api.md
+++ b/api.md
@@ -80,6 +80,8 @@ Types:
```python
from kernel.types import (
+ BrowserMemory,
+ BrowserMemoryRequest,
BrowserNetworkConfig,
BrowserPoolRef,
BrowserProxy,
diff --git a/src/kernel/resources/browsers/browsers.py b/src/kernel/resources/browsers/browsers.py
index 7e46a361..bf80a931 100644
--- a/src/kernel/resources/browsers/browsers.py
+++ b/src/kernel/resources/browsers/browsers.py
@@ -25,6 +25,7 @@
AsyncFsResourceWithStreamingResponse,
)
from ...types import (
+ BrowserMemoryRequest,
browser_curl_params,
browser_list_params,
browser_create_params,
@@ -94,6 +95,7 @@
async_stream_via_browser_route,
async_request_via_browser_route,
)
+from ...types.browser_memory_request import BrowserMemoryRequest
from ...types.browser_create_response import BrowserCreateResponse
from ...types.browser_update_response import BrowserUpdateResponse
from ...types.browser_retrieve_response import BrowserRetrieveResponse
@@ -172,6 +174,7 @@ def create(
headless: bool | Omit = omit,
invocation_id: str | Omit = omit,
kiosk_mode: bool | Omit = omit,
+ memory: BrowserMemoryRequest | Omit = omit,
name: str | Omit = omit,
network: BrowserNetworkConfigParam | Omit = omit,
profile: BrowserProfile | Omit = omit,
@@ -213,6 +216,8 @@ def create(
kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live
view.
+ memory: Memory for a headful, non-GPU browser session. Defaults to 8GiB.
+
name: Optional human-readable name for the browser session, used to find it later in
the dashboard. Must be unique among active sessions within the project. Can be
changed later via PATCH /browsers/{id_or_name}.
@@ -295,6 +300,7 @@ def create(
"headless": headless,
"invocation_id": invocation_id,
"kiosk_mode": kiosk_mode,
+ "memory": memory,
"name": name,
"network": network,
"profile": profile,
@@ -786,6 +792,7 @@ async def create(
headless: bool | Omit = omit,
invocation_id: str | Omit = omit,
kiosk_mode: bool | Omit = omit,
+ memory: BrowserMemoryRequest | Omit = omit,
name: str | Omit = omit,
network: BrowserNetworkConfigParam | Omit = omit,
profile: BrowserProfile | Omit = omit,
@@ -827,6 +834,8 @@ async def create(
kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live
view.
+ memory: Memory for a headful, non-GPU browser session. Defaults to 8GiB.
+
name: Optional human-readable name for the browser session, used to find it later in
the dashboard. Must be unique among active sessions within the project. Can be
changed later via PATCH /browsers/{id_or_name}.
@@ -909,6 +918,7 @@ async def create(
"headless": headless,
"invocation_id": invocation_id,
"kiosk_mode": kiosk_mode,
+ "memory": memory,
"name": name,
"network": network,
"profile": profile,
diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py
index 13c85213..6373f24c 100644
--- a/src/kernel/types/__init__.py
+++ b/src/kernel/types/__init__.py
@@ -24,6 +24,7 @@
from .browser_pool import BrowserPool as BrowserPool
from .browser_proxy import BrowserProxy as BrowserProxy
from .browser_usage import BrowserUsage as BrowserUsage
+from .browser_memory import BrowserMemory as BrowserMemory
from .app_list_params import AppListParams as AppListParams
from .audit_log_entry import AuditLogEntry as AuditLogEntry
from .created_api_key import CreatedAPIKey as CreatedAPIKey
@@ -58,6 +59,7 @@
from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
from .proxy_create_response import ProxyCreateResponse as ProxyCreateResponse
from .proxy_update_response import ProxyUpdateResponse as ProxyUpdateResponse
+from .browser_memory_request import BrowserMemoryRequest as BrowserMemoryRequest
from .browser_network_config import BrowserNetworkConfig as BrowserNetworkConfig
from .credential_list_params import CredentialListParams as CredentialListParams
from .deployment_list_params import DeploymentListParams as DeploymentListParams
diff --git a/src/kernel/types/browser_create_params.py b/src/kernel/types/browser_create_params.py
index 0c280602..2899fb7c 100644
--- a/src/kernel/types/browser_create_params.py
+++ b/src/kernel/types/browser_create_params.py
@@ -6,6 +6,7 @@
from typing_extensions import Literal, TypedDict
from .tags_param import TagsParam
+from .browser_memory_request import BrowserMemoryRequest
from .browser_proxy_config_param import BrowserProxyConfigParam
from .browser_network_config_param import BrowserNetworkConfigParam
from .shared_params.browser_profile import BrowserProfile
@@ -58,6 +59,9 @@ class BrowserCreateParams(TypedDict, total=False):
view.
"""
+ memory: BrowserMemoryRequest
+ """Memory for a headful, non-GPU browser session. Defaults to 8GiB."""
+
name: str
"""
Optional human-readable name for the browser session, used to find it later in
diff --git a/src/kernel/types/browser_create_response.py b/src/kernel/types/browser_create_response.py
index f8d625e9..dcc96f43 100644
--- a/src/kernel/types/browser_create_response.py
+++ b/src/kernel/types/browser_create_response.py
@@ -9,6 +9,7 @@
from .._models import BaseModel
from .browser_proxy import BrowserProxy
from .browser_usage import BrowserUsage
+from .browser_memory import BrowserMemory
from .browser_pool_ref import BrowserPoolRef
from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
@@ -27,6 +28,9 @@ class BrowserCreateResponse(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
region: Literal["us-east", "eu-west"]
"""Geographic region of the browser session. Fixed once the session is created."""
diff --git a/src/kernel/types/browser_list_response.py b/src/kernel/types/browser_list_response.py
index 108bbef2..0ef81b9a 100644
--- a/src/kernel/types/browser_list_response.py
+++ b/src/kernel/types/browser_list_response.py
@@ -9,6 +9,7 @@
from .._models import BaseModel
from .browser_proxy import BrowserProxy
from .browser_usage import BrowserUsage
+from .browser_memory import BrowserMemory
from .browser_pool_ref import BrowserPoolRef
from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
@@ -27,6 +28,9 @@ class BrowserListResponse(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
region: Literal["us-east", "eu-west"]
"""Geographic region of the browser session. Fixed once the session is created."""
diff --git a/src/kernel/types/browser_memory.py b/src/kernel/types/browser_memory.py
new file mode 100644
index 00000000..b86a12aa
--- /dev/null
+++ b/src/kernel/types/browser_memory.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["BrowserMemory"]
+
+BrowserMemory: TypeAlias = Literal["1GiB", "2GiB", "6GiB", "8GiB", "16GiB"]
diff --git a/src/kernel/types/browser_memory_request.py b/src/kernel/types/browser_memory_request.py
new file mode 100644
index 00000000..f2363b71
--- /dev/null
+++ b/src/kernel/types/browser_memory_request.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["BrowserMemoryRequest"]
+
+BrowserMemoryRequest: TypeAlias = Literal["8GiB", "16GiB"]
diff --git a/src/kernel/types/browser_pool_acquire_response.py b/src/kernel/types/browser_pool_acquire_response.py
index 720dc569..d9638105 100644
--- a/src/kernel/types/browser_pool_acquire_response.py
+++ b/src/kernel/types/browser_pool_acquire_response.py
@@ -9,6 +9,7 @@
from .._models import BaseModel
from .browser_proxy import BrowserProxy
from .browser_usage import BrowserUsage
+from .browser_memory import BrowserMemory
from .browser_pool_ref import BrowserPoolRef
from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
@@ -27,6 +28,9 @@ class BrowserPoolAcquireResponse(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
region: Literal["us-east", "eu-west"]
"""Geographic region of the browser session. Fixed once the session is created."""
diff --git a/src/kernel/types/browser_retrieve_response.py b/src/kernel/types/browser_retrieve_response.py
index 8e39a607..8292024b 100644
--- a/src/kernel/types/browser_retrieve_response.py
+++ b/src/kernel/types/browser_retrieve_response.py
@@ -9,6 +9,7 @@
from .._models import BaseModel
from .browser_proxy import BrowserProxy
from .browser_usage import BrowserUsage
+from .browser_memory import BrowserMemory
from .browser_pool_ref import BrowserPoolRef
from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
@@ -27,6 +28,9 @@ class BrowserRetrieveResponse(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
region: Literal["us-east", "eu-west"]
"""Geographic region of the browser session. Fixed once the session is created."""
diff --git a/src/kernel/types/browser_update_response.py b/src/kernel/types/browser_update_response.py
index c04f43f8..7dd7de88 100644
--- a/src/kernel/types/browser_update_response.py
+++ b/src/kernel/types/browser_update_response.py
@@ -9,6 +9,7 @@
from .._models import BaseModel
from .browser_proxy import BrowserProxy
from .browser_usage import BrowserUsage
+from .browser_memory import BrowserMemory
from .browser_pool_ref import BrowserPoolRef
from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
@@ -27,6 +28,9 @@ class BrowserUpdateResponse(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
region: Literal["us-east", "eu-west"]
"""Geographic region of the browser session. Fixed once the session is created."""
diff --git a/src/kernel/types/invocation_list_browsers_response.py b/src/kernel/types/invocation_list_browsers_response.py
index bcf8dd40..db32954d 100644
--- a/src/kernel/types/invocation_list_browsers_response.py
+++ b/src/kernel/types/invocation_list_browsers_response.py
@@ -9,6 +9,7 @@
from .._models import BaseModel
from .browser_proxy import BrowserProxy
from .browser_usage import BrowserUsage
+from .browser_memory import BrowserMemory
from .browser_pool_ref import BrowserPoolRef
from .browser_network_config import BrowserNetworkConfig
from .shared.browser_viewport import BrowserViewport
@@ -27,6 +28,9 @@ class Browser(BaseModel):
headless: bool
"""Whether the browser session is running in headless mode."""
+ memory: BrowserMemory
+ """Memory allocated to the browser session."""
+
region: Literal["us-east", "eu-west"]
"""Geographic region of the browser session. Fixed once the session is created."""
diff --git a/tests/api_resources/test_browsers.py b/tests/api_resources/test_browsers.py
index ae017186..1a352038 100644
--- a/tests/api_resources/test_browsers.py
+++ b/tests/api_resources/test_browsers.py
@@ -45,6 +45,7 @@ def test_method_create_with_all_params(self, client: Kernel) -> None:
headless=False,
invocation_id="rr33xuugxj9h0bkf1rdt2bet",
kiosk_mode=True,
+ memory="8GiB",
name="checkout-flow-1",
network={"private_hosts": ["*.example.ts.net", "100.64.0.0/10"]},
profile={
@@ -503,6 +504,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) ->
headless=False,
invocation_id="rr33xuugxj9h0bkf1rdt2bet",
kiosk_mode=True,
+ memory="8GiB",
name="checkout-flow-1",
network={"private_hosts": ["*.example.ts.net", "100.64.0.0/10"]},
profile={
diff --git a/tests/test_browser_routing.py b/tests/test_browser_routing.py
index 84c5c541..3538c221 100644
--- a/tests/test_browser_routing.py
+++ b/tests/test_browser_routing.py
@@ -31,6 +31,7 @@ def _fake_browser() -> dict[str, object]:
"created_at": "2020-01-01T00:00:00Z",
"headless": True,
"stealth": False,
+ "memory": "8GiB",
"timeout_seconds": 60,
"region": "us-east",
}
From 8479adff62631c996a4a9129fd8639c16d96b92b Mon Sep 17 00:00:00 2001
From: "kernel-internal[bot]"
<260533166+kernel-internal[bot]@users.noreply.github.com>
Date: Fri, 14 Aug 2026 18:08:26 +0000
Subject: [PATCH 3/4] feat: Add customer-facing OTLP destination CRUD API
Stainless-Generated-From: fc9d70b47afce5656d62cc298600a5e249ea0d63
---
.stats.yml | 2 +-
api.md | 18 +
src/kernel/_client.py | 38 +
src/kernel/resources/__init__.py | 14 +
src/kernel/resources/browsers/browsers.py | 24 +-
src/kernel/resources/browsers/telemetry.py | 8 +-
src/kernel/resources/telemetry/__init__.py | 33 +
.../resources/telemetry/destinations.py | 676 ++++++++++++++++++
src/kernel/resources/telemetry/telemetry.py | 120 ++++
src/kernel/types/telemetry/__init__.py | 8 +
.../telemetry/destination_create_params.py | 39 +
.../telemetry/destination_list_params.py | 27 +
.../telemetry/destination_update_params.py | 31 +
.../types/telemetry/otlp_destination.py | 41 ++
tests/api_resources/telemetry/__init__.py | 1 +
.../telemetry/test_destinations.py | 477 ++++++++++++
16 files changed, 1548 insertions(+), 9 deletions(-)
create mode 100644 src/kernel/resources/telemetry/__init__.py
create mode 100644 src/kernel/resources/telemetry/destinations.py
create mode 100644 src/kernel/resources/telemetry/telemetry.py
create mode 100644 src/kernel/types/telemetry/__init__.py
create mode 100644 src/kernel/types/telemetry/destination_create_params.py
create mode 100644 src/kernel/types/telemetry/destination_list_params.py
create mode 100644 src/kernel/types/telemetry/destination_update_params.py
create mode 100644 src/kernel/types/telemetry/otlp_destination.py
create mode 100644 tests/api_resources/telemetry/__init__.py
create mode 100644 tests/api_resources/telemetry/test_destinations.py
diff --git a/.stats.yml b/.stats.yml
index 50996d49..98f97da5 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 134
+configured_endpoints: 139
diff --git a/api.md b/api.md
index 52d4e4c3..62ade185 100644
--- a/api.md
+++ b/api.md
@@ -342,6 +342,24 @@ Methods:
- client.auth.connections.submit(id, \*\*params) -> SubmitFieldsResponse
- client.auth.connections.timeline(id, \*\*params) -> SyncOffsetPagination[ManagedAuthTimelineEvent]
+# Telemetry
+
+## Destinations
+
+Types:
+
+```python
+from kernel.types.telemetry import OtlpDestination
+```
+
+Methods:
+
+- client.telemetry.destinations.create(\*\*params) -> OtlpDestination
+- client.telemetry.destinations.retrieve(id_or_name) -> OtlpDestination
+- client.telemetry.destinations.update(id_or_name, \*\*params) -> OtlpDestination
+- client.telemetry.destinations.list(\*\*params) -> SyncOffsetPagination[OtlpDestination]
+- client.telemetry.destinations.delete(id_or_name) -> None
+
# Proxies
Types:
diff --git a/src/kernel/_client.py b/src/kernel/_client.py
index eb4d1af1..497cc5d5 100644
--- a/src/kernel/_client.py
+++ b/src/kernel/_client.py
@@ -54,6 +54,7 @@
browsers,
profiles,
projects,
+ telemetry,
audit_logs,
extensions,
credentials,
@@ -75,6 +76,7 @@
from .resources.browser_pools import BrowserPoolsResource, AsyncBrowserPoolsResource
from .resources.browsers.browsers import BrowsersResource, AsyncBrowsersResource
from .resources.projects.projects import ProjectsResource, AsyncProjectsResource
+ from .resources.telemetry.telemetry import TelemetryResource, AsyncTelemetryResource
from .resources.credential_providers import CredentialProvidersResource, AsyncCredentialProvidersResource
from .resources.audit_logs.audit_logs import AuditLogsResource, AsyncAuditLogsResource
from .resources.organization.organization import OrganizationResource, AsyncOrganizationResource
@@ -240,6 +242,12 @@ def auth(self) -> AuthResource:
return AuthResource(self)
+ @cached_property
+ def telemetry(self) -> TelemetryResource:
+ from .resources.telemetry import TelemetryResource
+
+ return TelemetryResource(self)
+
@cached_property
def proxies(self) -> ProxiesResource:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -603,6 +611,12 @@ def auth(self) -> AsyncAuthResource:
return AsyncAuthResource(self)
+ @cached_property
+ def telemetry(self) -> AsyncTelemetryResource:
+ from .resources.telemetry import AsyncTelemetryResource
+
+ return AsyncTelemetryResource(self)
+
@cached_property
def proxies(self) -> AsyncProxiesResource:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -870,6 +884,12 @@ def auth(self) -> auth.AuthResourceWithRawResponse:
return AuthResourceWithRawResponse(self._client.auth)
+ @cached_property
+ def telemetry(self) -> telemetry.TelemetryResourceWithRawResponse:
+ from .resources.telemetry import TelemetryResourceWithRawResponse
+
+ return TelemetryResourceWithRawResponse(self._client.telemetry)
+
@cached_property
def proxies(self) -> proxies.ProxiesResourceWithRawResponse:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -984,6 +1004,12 @@ def auth(self) -> auth.AsyncAuthResourceWithRawResponse:
return AsyncAuthResourceWithRawResponse(self._client.auth)
+ @cached_property
+ def telemetry(self) -> telemetry.AsyncTelemetryResourceWithRawResponse:
+ from .resources.telemetry import AsyncTelemetryResourceWithRawResponse
+
+ return AsyncTelemetryResourceWithRawResponse(self._client.telemetry)
+
@cached_property
def proxies(self) -> proxies.AsyncProxiesResourceWithRawResponse:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -1098,6 +1124,12 @@ def auth(self) -> auth.AuthResourceWithStreamingResponse:
return AuthResourceWithStreamingResponse(self._client.auth)
+ @cached_property
+ def telemetry(self) -> telemetry.TelemetryResourceWithStreamingResponse:
+ from .resources.telemetry import TelemetryResourceWithStreamingResponse
+
+ return TelemetryResourceWithStreamingResponse(self._client.telemetry)
+
@cached_property
def proxies(self) -> proxies.ProxiesResourceWithStreamingResponse:
"""Create and manage proxy configurations for routing browser traffic."""
@@ -1212,6 +1244,12 @@ def auth(self) -> auth.AsyncAuthResourceWithStreamingResponse:
return AsyncAuthResourceWithStreamingResponse(self._client.auth)
+ @cached_property
+ def telemetry(self) -> telemetry.AsyncTelemetryResourceWithStreamingResponse:
+ from .resources.telemetry import AsyncTelemetryResourceWithStreamingResponse
+
+ return AsyncTelemetryResourceWithStreamingResponse(self._client.telemetry)
+
@cached_property
def proxies(self) -> proxies.AsyncProxiesResourceWithStreamingResponse:
"""Create and manage proxy configurations for routing browser traffic."""
diff --git a/src/kernel/resources/__init__.py b/src/kernel/resources/__init__.py
index 586c499a..10f1a88b 100644
--- a/src/kernel/resources/__init__.py
+++ b/src/kernel/resources/__init__.py
@@ -56,6 +56,14 @@
ProjectsResourceWithStreamingResponse,
AsyncProjectsResourceWithStreamingResponse,
)
+from .telemetry import (
+ TelemetryResource,
+ AsyncTelemetryResource,
+ TelemetryResourceWithRawResponse,
+ AsyncTelemetryResourceWithRawResponse,
+ TelemetryResourceWithStreamingResponse,
+ AsyncTelemetryResourceWithStreamingResponse,
+)
from .audit_logs import (
AuditLogsResource,
AsyncAuditLogsResource,
@@ -158,6 +166,12 @@
"AsyncAuthResourceWithRawResponse",
"AuthResourceWithStreamingResponse",
"AsyncAuthResourceWithStreamingResponse",
+ "TelemetryResource",
+ "AsyncTelemetryResource",
+ "TelemetryResourceWithRawResponse",
+ "AsyncTelemetryResourceWithRawResponse",
+ "TelemetryResourceWithStreamingResponse",
+ "AsyncTelemetryResourceWithStreamingResponse",
"ProxiesResource",
"AsyncProxiesResource",
"ProxiesResourceWithRawResponse",
diff --git a/src/kernel/resources/browsers/browsers.py b/src/kernel/resources/browsers/browsers.py
index bf80a931..8078b952 100644
--- a/src/kernel/resources/browsers/browsers.py
+++ b/src/kernel/resources/browsers/browsers.py
@@ -113,7 +113,9 @@ class BrowsersResource(SyncAPIResource):
@cached_property
def telemetry(self) -> TelemetryResource:
- """Stream live telemetry events from a browser session."""
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
return TelemetryResource(self._client)
@cached_property
@@ -731,7 +733,9 @@ class AsyncBrowsersResource(AsyncAPIResource):
@cached_property
def telemetry(self) -> AsyncTelemetryResource:
- """Stream live telemetry events from a browser session."""
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
return AsyncTelemetryResource(self._client)
@cached_property
@@ -1372,7 +1376,9 @@ def __init__(self, browsers: BrowsersResource) -> None:
@cached_property
def telemetry(self) -> TelemetryResourceWithRawResponse:
- """Stream live telemetry events from a browser session."""
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
return TelemetryResourceWithRawResponse(self._browsers.telemetry)
@cached_property
@@ -1434,7 +1440,9 @@ def __init__(self, browsers: AsyncBrowsersResource) -> None:
@cached_property
def telemetry(self) -> AsyncTelemetryResourceWithRawResponse:
- """Stream live telemetry events from a browser session."""
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
return AsyncTelemetryResourceWithRawResponse(self._browsers.telemetry)
@cached_property
@@ -1496,7 +1504,9 @@ def __init__(self, browsers: BrowsersResource) -> None:
@cached_property
def telemetry(self) -> TelemetryResourceWithStreamingResponse:
- """Stream live telemetry events from a browser session."""
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
return TelemetryResourceWithStreamingResponse(self._browsers.telemetry)
@cached_property
@@ -1558,7 +1568,9 @@ def __init__(self, browsers: AsyncBrowsersResource) -> None:
@cached_property
def telemetry(self) -> AsyncTelemetryResourceWithStreamingResponse:
- """Stream live telemetry events from a browser session."""
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
return AsyncTelemetryResourceWithStreamingResponse(self._browsers.telemetry)
@cached_property
diff --git a/src/kernel/resources/browsers/telemetry.py b/src/kernel/resources/browsers/telemetry.py
index 4c7769e7..5ed61557 100644
--- a/src/kernel/resources/browsers/telemetry.py
+++ b/src/kernel/resources/browsers/telemetry.py
@@ -28,7 +28,9 @@
class TelemetryResource(SyncAPIResource):
- """Stream live telemetry events from a browser session."""
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
@cached_property
def with_raw_response(self) -> TelemetryResourceWithRawResponse:
@@ -202,7 +204,9 @@ def stream(
class AsyncTelemetryResource(AsyncAPIResource):
- """Stream live telemetry events from a browser session."""
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
@cached_property
def with_raw_response(self) -> AsyncTelemetryResourceWithRawResponse:
diff --git a/src/kernel/resources/telemetry/__init__.py b/src/kernel/resources/telemetry/__init__.py
new file mode 100644
index 00000000..77034151
--- /dev/null
+++ b/src/kernel/resources/telemetry/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .telemetry import (
+ TelemetryResource,
+ AsyncTelemetryResource,
+ TelemetryResourceWithRawResponse,
+ AsyncTelemetryResourceWithRawResponse,
+ TelemetryResourceWithStreamingResponse,
+ AsyncTelemetryResourceWithStreamingResponse,
+)
+from .destinations import (
+ DestinationsResource,
+ AsyncDestinationsResource,
+ DestinationsResourceWithRawResponse,
+ AsyncDestinationsResourceWithRawResponse,
+ DestinationsResourceWithStreamingResponse,
+ AsyncDestinationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "DestinationsResource",
+ "AsyncDestinationsResource",
+ "DestinationsResourceWithRawResponse",
+ "AsyncDestinationsResourceWithRawResponse",
+ "DestinationsResourceWithStreamingResponse",
+ "AsyncDestinationsResourceWithStreamingResponse",
+ "TelemetryResource",
+ "AsyncTelemetryResource",
+ "TelemetryResourceWithRawResponse",
+ "AsyncTelemetryResourceWithRawResponse",
+ "TelemetryResourceWithStreamingResponse",
+ "AsyncTelemetryResourceWithStreamingResponse",
+]
diff --git a/src/kernel/resources/telemetry/destinations.py b/src/kernel/resources/telemetry/destinations.py
new file mode 100644
index 00000000..09442435
--- /dev/null
+++ b/src/kernel/resources/telemetry/destinations.py
@@ -0,0 +1,676 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+
+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 ...pagination import SyncOffsetPagination, AsyncOffsetPagination
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.telemetry import destination_list_params, destination_create_params, destination_update_params
+from ...types.telemetry.otlp_destination import OtlpDestination
+
+__all__ = ["DestinationsResource", "AsyncDestinationsResource"]
+
+
+class DestinationsResource(SyncAPIResource):
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> DestinationsResourceWithRawResponse:
+ """
+ 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 DestinationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> DestinationsResourceWithStreamingResponse:
+ """
+ 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 DestinationsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ endpoint: str,
+ name: str,
+ description: str | Omit = omit,
+ headers: 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.
+ # 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,
+ ) -> OtlpDestination:
+ """Create an OTLP export destination in the resolved project.
+
+ Names must be unique
+ within the project.
+
+ Args:
+ endpoint: Base endpoint of the OTLP/HTTP collector, without a signal path. Kernel appends
+ the signal path itself, so pass `https://api.honeycomb.io` rather than
+ `https://api.honeycomb.io/v1/logs`. If your provider's docs give you a
+ signal-specific URL, drop the trailing `/v1/logs`, `/v1/traces`, or
+ `/v1/metrics` — an endpoint that already carries one is rejected.
+
+ Must be http or https, must resolve to a public address, and must carry no query
+ string or fragment. Examples: `https://api.honeycomb.io`,
+ `https://otlp-gateway-prod-us-east-0.grafana.net/otlp`,
+ `https://otlp.datadoghq.com` (Datadog's OTLP intake for US1, not its logs
+ intake).
+
+ name: Unique within the project.
+
+ headers: Headers sent with each export request, typically an ingestion key. Encrypted at
+ rest and returned redacted. Names and values must be valid HTTP header tokens,
+ and the names and values together cannot exceed 8192 bytes. Names are matched
+ case-insensitively and stored canonicalized, so supplying two spellings of one
+ header is rejected.
+
+ 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._post(
+ "/telemetry/destinations",
+ body=maybe_transform(
+ {
+ "endpoint": endpoint,
+ "name": name,
+ "description": description,
+ "headers": headers,
+ },
+ destination_create_params.DestinationCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OtlpDestination,
+ )
+
+ def retrieve(
+ 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,
+ ) -> OtlpDestination:
+ """
+ Retrieve a single OTLP destination in the resolved project by its ID or name.
+
+ 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("/telemetry/destinations/{id_or_name}", 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=OtlpDestination,
+ )
+
+ def update(
+ self,
+ id_or_name: str,
+ *,
+ description: str | Omit = omit,
+ endpoint: str | Omit = omit,
+ headers: Dict[str, Optional[str]] | Omit = omit,
+ name: str | 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,
+ ) -> OtlpDestination:
+ """Update an OTLP destination.
+
+ Sessions already exporting to it pick up the new
+ values without restarting, which makes this the way to rotate credentials
+ without interrupting export.
+
+ Names must be unique within the project. Renaming is refused with a 409 while a
+ managed auth connection selects this destination by name, since that connection
+ resolves the name on every login. Every other field, including `headers`, stays
+ editable.
+
+ Args:
+ endpoint: Base endpoint of the OTLP/HTTP collector, without a signal path. Same rules as
+ on create.
+
+ headers: Edits stored headers key by key rather than replacing the map. A string value
+ adds or replaces that header, `null` deletes it, and any key you omit is left as
+ it is. Names are matched case-insensitively, so `authorization` replaces a
+ stored `Authorization` rather than adding a second entry. This is the credential
+ rotation path; sessions already exporting pick up the new values without
+ restarting. Names and values must be valid HTTP header tokens, and the names and
+ values together cannot exceed 8192 bytes.
+
+ 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._patch(
+ path_template("/telemetry/destinations/{id_or_name}", id_or_name=id_or_name),
+ body=maybe_transform(
+ {
+ "description": description,
+ "endpoint": endpoint,
+ "headers": headers,
+ "name": name,
+ },
+ destination_update_params.DestinationUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OtlpDestination,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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,
+ ) -> SyncOffsetPagination[OtlpDestination]:
+ """
+ List OTLP export destinations in the resolved project.
+
+ Args:
+ limit: Limit the number of destinations to return.
+
+ name: Exact-match filter on destination name using the database collation. In
+ production, matching is case- and accent-insensitive.
+
+ offset: Offset the number of destinations to return.
+
+ query: Case-insensitive substring match against destination name or endpoint. IDs match
+ by exact value.
+
+ 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(
+ "/telemetry/destinations",
+ page=SyncOffsetPagination[OtlpDestination],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ },
+ destination_list_params.DestinationListParams,
+ ),
+ ),
+ model=OtlpDestination,
+ )
+
+ def delete(
+ 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,
+ ) -> None:
+ """Delete an OTLP destination.
+
+ Sessions bound to it are still exporting, so the
+ delete is refused with a 409 while any exist; either wait for those sessions to
+ end or delete them first. It is refused the same way while a managed auth
+ connection still selects it, because that connection re-resolves the destination
+ on every login, and while a managed auth login using it is still in progress.
+
+ 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}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/telemetry/destinations/{id_or_name}", 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=NoneType,
+ )
+
+
+class AsyncDestinationsResource(AsyncAPIResource):
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncDestinationsResourceWithRawResponse:
+ """
+ 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 AsyncDestinationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncDestinationsResourceWithStreamingResponse:
+ """
+ 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 AsyncDestinationsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ endpoint: str,
+ name: str,
+ description: str | Omit = omit,
+ headers: 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.
+ # 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,
+ ) -> OtlpDestination:
+ """Create an OTLP export destination in the resolved project.
+
+ Names must be unique
+ within the project.
+
+ Args:
+ endpoint: Base endpoint of the OTLP/HTTP collector, without a signal path. Kernel appends
+ the signal path itself, so pass `https://api.honeycomb.io` rather than
+ `https://api.honeycomb.io/v1/logs`. If your provider's docs give you a
+ signal-specific URL, drop the trailing `/v1/logs`, `/v1/traces`, or
+ `/v1/metrics` — an endpoint that already carries one is rejected.
+
+ Must be http or https, must resolve to a public address, and must carry no query
+ string or fragment. Examples: `https://api.honeycomb.io`,
+ `https://otlp-gateway-prod-us-east-0.grafana.net/otlp`,
+ `https://otlp.datadoghq.com` (Datadog's OTLP intake for US1, not its logs
+ intake).
+
+ name: Unique within the project.
+
+ headers: Headers sent with each export request, typically an ingestion key. Encrypted at
+ rest and returned redacted. Names and values must be valid HTTP header tokens,
+ and the names and values together cannot exceed 8192 bytes. Names are matched
+ case-insensitively and stored canonicalized, so supplying two spellings of one
+ header is rejected.
+
+ 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 await self._post(
+ "/telemetry/destinations",
+ body=await async_maybe_transform(
+ {
+ "endpoint": endpoint,
+ "name": name,
+ "description": description,
+ "headers": headers,
+ },
+ destination_create_params.DestinationCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OtlpDestination,
+ )
+
+ async def retrieve(
+ 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,
+ ) -> OtlpDestination:
+ """
+ Retrieve a single OTLP destination in the resolved project by its ID or name.
+
+ 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("/telemetry/destinations/{id_or_name}", 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=OtlpDestination,
+ )
+
+ async def update(
+ self,
+ id_or_name: str,
+ *,
+ description: str | Omit = omit,
+ endpoint: str | Omit = omit,
+ headers: Dict[str, Optional[str]] | Omit = omit,
+ name: str | 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,
+ ) -> OtlpDestination:
+ """Update an OTLP destination.
+
+ Sessions already exporting to it pick up the new
+ values without restarting, which makes this the way to rotate credentials
+ without interrupting export.
+
+ Names must be unique within the project. Renaming is refused with a 409 while a
+ managed auth connection selects this destination by name, since that connection
+ resolves the name on every login. Every other field, including `headers`, stays
+ editable.
+
+ Args:
+ endpoint: Base endpoint of the OTLP/HTTP collector, without a signal path. Same rules as
+ on create.
+
+ headers: Edits stored headers key by key rather than replacing the map. A string value
+ adds or replaces that header, `null` deletes it, and any key you omit is left as
+ it is. Names are matched case-insensitively, so `authorization` replaces a
+ stored `Authorization` rather than adding a second entry. This is the credential
+ rotation path; sessions already exporting pick up the new values without
+ restarting. Names and values must be valid HTTP header tokens, and the names and
+ values together cannot exceed 8192 bytes.
+
+ 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._patch(
+ path_template("/telemetry/destinations/{id_or_name}", id_or_name=id_or_name),
+ body=await async_maybe_transform(
+ {
+ "description": description,
+ "endpoint": endpoint,
+ "headers": headers,
+ "name": name,
+ },
+ destination_update_params.DestinationUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=OtlpDestination,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ name: str | Omit = omit,
+ offset: int | Omit = omit,
+ query: str | 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,
+ ) -> AsyncPaginator[OtlpDestination, AsyncOffsetPagination[OtlpDestination]]:
+ """
+ List OTLP export destinations in the resolved project.
+
+ Args:
+ limit: Limit the number of destinations to return.
+
+ name: Exact-match filter on destination name using the database collation. In
+ production, matching is case- and accent-insensitive.
+
+ offset: Offset the number of destinations to return.
+
+ query: Case-insensitive substring match against destination name or endpoint. IDs match
+ by exact value.
+
+ 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(
+ "/telemetry/destinations",
+ page=AsyncOffsetPagination[OtlpDestination],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "name": name,
+ "offset": offset,
+ "query": query,
+ },
+ destination_list_params.DestinationListParams,
+ ),
+ ),
+ model=OtlpDestination,
+ )
+
+ async def delete(
+ 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,
+ ) -> None:
+ """Delete an OTLP destination.
+
+ Sessions bound to it are still exporting, so the
+ delete is refused with a 409 while any exist; either wait for those sessions to
+ end or delete them first. It is refused the same way while a managed auth
+ connection still selects it, because that connection re-resolves the destination
+ on every login, and while a managed auth login using it is still in progress.
+
+ 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}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/telemetry/destinations/{id_or_name}", 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=NoneType,
+ )
+
+
+class DestinationsResourceWithRawResponse:
+ def __init__(self, destinations: DestinationsResource) -> None:
+ self._destinations = destinations
+
+ self.create = to_raw_response_wrapper(
+ destinations.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ destinations.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ destinations.update,
+ )
+ self.list = to_raw_response_wrapper(
+ destinations.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ destinations.delete,
+ )
+
+
+class AsyncDestinationsResourceWithRawResponse:
+ def __init__(self, destinations: AsyncDestinationsResource) -> None:
+ self._destinations = destinations
+
+ self.create = async_to_raw_response_wrapper(
+ destinations.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ destinations.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ destinations.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ destinations.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ destinations.delete,
+ )
+
+
+class DestinationsResourceWithStreamingResponse:
+ def __init__(self, destinations: DestinationsResource) -> None:
+ self._destinations = destinations
+
+ self.create = to_streamed_response_wrapper(
+ destinations.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ destinations.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ destinations.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ destinations.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ destinations.delete,
+ )
+
+
+class AsyncDestinationsResourceWithStreamingResponse:
+ def __init__(self, destinations: AsyncDestinationsResource) -> None:
+ self._destinations = destinations
+
+ self.create = async_to_streamed_response_wrapper(
+ destinations.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ destinations.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ destinations.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ destinations.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ destinations.delete,
+ )
diff --git a/src/kernel/resources/telemetry/telemetry.py b/src/kernel/resources/telemetry/telemetry.py
new file mode 100644
index 00000000..afdb7434
--- /dev/null
+++ b/src/kernel/resources/telemetry/telemetry.py
@@ -0,0 +1,120 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from .destinations import (
+ DestinationsResource,
+ AsyncDestinationsResource,
+ DestinationsResourceWithRawResponse,
+ AsyncDestinationsResourceWithRawResponse,
+ DestinationsResourceWithStreamingResponse,
+ AsyncDestinationsResourceWithStreamingResponse,
+)
+
+__all__ = ["TelemetryResource", "AsyncTelemetryResource"]
+
+
+class TelemetryResource(SyncAPIResource):
+ @cached_property
+ def destinations(self) -> DestinationsResource:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return DestinationsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> TelemetryResourceWithRawResponse:
+ """
+ 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 TelemetryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> TelemetryResourceWithStreamingResponse:
+ """
+ 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 TelemetryResourceWithStreamingResponse(self)
+
+
+class AsyncTelemetryResource(AsyncAPIResource):
+ @cached_property
+ def destinations(self) -> AsyncDestinationsResource:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return AsyncDestinationsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncTelemetryResourceWithRawResponse:
+ """
+ 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 AsyncTelemetryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncTelemetryResourceWithStreamingResponse:
+ """
+ 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 AsyncTelemetryResourceWithStreamingResponse(self)
+
+
+class TelemetryResourceWithRawResponse:
+ def __init__(self, telemetry: TelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ @cached_property
+ def destinations(self) -> DestinationsResourceWithRawResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return DestinationsResourceWithRawResponse(self._telemetry.destinations)
+
+
+class AsyncTelemetryResourceWithRawResponse:
+ def __init__(self, telemetry: AsyncTelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ @cached_property
+ def destinations(self) -> AsyncDestinationsResourceWithRawResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return AsyncDestinationsResourceWithRawResponse(self._telemetry.destinations)
+
+
+class TelemetryResourceWithStreamingResponse:
+ def __init__(self, telemetry: TelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ @cached_property
+ def destinations(self) -> DestinationsResourceWithStreamingResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return DestinationsResourceWithStreamingResponse(self._telemetry.destinations)
+
+
+class AsyncTelemetryResourceWithStreamingResponse:
+ def __init__(self, telemetry: AsyncTelemetryResource) -> None:
+ self._telemetry = telemetry
+
+ @cached_property
+ def destinations(self) -> AsyncDestinationsResourceWithStreamingResponse:
+ """
+ Stream live telemetry events from a browser session, and manage the destinations sessions export them to.
+ """
+ return AsyncDestinationsResourceWithStreamingResponse(self._telemetry.destinations)
diff --git a/src/kernel/types/telemetry/__init__.py b/src/kernel/types/telemetry/__init__.py
new file mode 100644
index 00000000..e0a465df
--- /dev/null
+++ b/src/kernel/types/telemetry/__init__.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .otlp_destination import OtlpDestination as OtlpDestination
+from .destination_list_params import DestinationListParams as DestinationListParams
+from .destination_create_params import DestinationCreateParams as DestinationCreateParams
+from .destination_update_params import DestinationUpdateParams as DestinationUpdateParams
diff --git a/src/kernel/types/telemetry/destination_create_params.py b/src/kernel/types/telemetry/destination_create_params.py
new file mode 100644
index 00000000..882f7cc6
--- /dev/null
+++ b/src/kernel/types/telemetry/destination_create_params.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Required, TypedDict
+
+__all__ = ["DestinationCreateParams"]
+
+
+class DestinationCreateParams(TypedDict, total=False):
+ endpoint: Required[str]
+ """Base endpoint of the OTLP/HTTP collector, without a signal path.
+
+ Kernel appends the signal path itself, so pass `https://api.honeycomb.io` rather
+ than `https://api.honeycomb.io/v1/logs`. If your provider's docs give you a
+ signal-specific URL, drop the trailing `/v1/logs`, `/v1/traces`, or
+ `/v1/metrics` — an endpoint that already carries one is rejected.
+
+ Must be http or https, must resolve to a public address, and must carry no query
+ string or fragment. Examples: `https://api.honeycomb.io`,
+ `https://otlp-gateway-prod-us-east-0.grafana.net/otlp`,
+ `https://otlp.datadoghq.com` (Datadog's OTLP intake for US1, not its logs
+ intake).
+ """
+
+ name: Required[str]
+ """Unique within the project."""
+
+ description: str
+
+ headers: Dict[str, str]
+ """Headers sent with each export request, typically an ingestion key.
+
+ Encrypted at rest and returned redacted. Names and values must be valid HTTP
+ header tokens, and the names and values together cannot exceed 8192 bytes. Names
+ are matched case-insensitively and stored canonicalized, so supplying two
+ spellings of one header is rejected.
+ """
diff --git a/src/kernel/types/telemetry/destination_list_params.py b/src/kernel/types/telemetry/destination_list_params.py
new file mode 100644
index 00000000..fa7a1dbe
--- /dev/null
+++ b/src/kernel/types/telemetry/destination_list_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["DestinationListParams"]
+
+
+class DestinationListParams(TypedDict, total=False):
+ limit: int
+ """Limit the number of destinations to return."""
+
+ name: str
+ """Exact-match filter on destination name using the database collation.
+
+ In production, matching is case- and accent-insensitive.
+ """
+
+ offset: int
+ """Offset the number of destinations to return."""
+
+ query: str
+ """Case-insensitive substring match against destination name or endpoint.
+
+ IDs match by exact value.
+ """
diff --git a/src/kernel/types/telemetry/destination_update_params.py b/src/kernel/types/telemetry/destination_update_params.py
new file mode 100644
index 00000000..266b8029
--- /dev/null
+++ b/src/kernel/types/telemetry/destination_update_params.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+from typing_extensions import TypedDict
+
+__all__ = ["DestinationUpdateParams"]
+
+
+class DestinationUpdateParams(TypedDict, total=False):
+ description: str
+
+ endpoint: str
+ """Base endpoint of the OTLP/HTTP collector, without a signal path.
+
+ Same rules as on create.
+ """
+
+ headers: Dict[str, Optional[str]]
+ """Edits stored headers key by key rather than replacing the map.
+
+ A string value adds or replaces that header, `null` deletes it, and any key you
+ omit is left as it is. Names are matched case-insensitively, so `authorization`
+ replaces a stored `Authorization` rather than adding a second entry. This is the
+ credential rotation path; sessions already exporting pick up the new values
+ without restarting. Names and values must be valid HTTP header tokens, and the
+ names and values together cannot exceed 8192 bytes.
+ """
+
+ name: str
diff --git a/src/kernel/types/telemetry/otlp_destination.py b/src/kernel/types/telemetry/otlp_destination.py
new file mode 100644
index 00000000..c6e6f2bc
--- /dev/null
+++ b/src/kernel/types/telemetry/otlp_destination.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["OtlpDestination"]
+
+
+class OtlpDestination(BaseModel):
+ """An OTLP endpoint to export browser session telemetry to.
+
+ Reference one from `telemetry.export.otlp.destination` when creating a browser to export that session's captured telemetry to it.
+ """
+
+ id: str
+
+ created_at: datetime
+
+ endpoint: str
+ """OTLP/HTTP endpoint telemetry is sent to."""
+
+ headers: Dict[str, str]
+ """Headers sent with each export request.
+
+ Names are returned in canonical form (`Authorization`, not `authorization`).
+ Values are returned redacted as empty strings, so the keys are visible but the
+ credentials are not.
+ """
+
+ name: str
+ """Unique within the project.
+
+ Usable in place of the ID when selecting a destination, so it cannot be shaped
+ like an ID.
+ """
+
+ updated_at: datetime
+
+ description: Optional[str] = None
diff --git a/tests/api_resources/telemetry/__init__.py b/tests/api_resources/telemetry/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/telemetry/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/telemetry/test_destinations.py b/tests/api_resources/telemetry/test_destinations.py
new file mode 100644
index 00000000..ddf06c0d
--- /dev/null
+++ b/tests/api_resources/telemetry/test_destinations.py
@@ -0,0 +1,477 @@
+# 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.pagination import SyncOffsetPagination, AsyncOffsetPagination
+from kernel.types.telemetry import (
+ OtlpDestination,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestDestinations:
+ 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_create(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.create(
+ endpoint="x",
+ name="name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.create(
+ endpoint="x",
+ name="name",
+ description="description",
+ headers={"foo": "string"},
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Kernel) -> None:
+ response = client.telemetry.destinations.with_raw_response.create(
+ endpoint="x",
+ name="name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Kernel) -> None:
+ with client.telemetry.destinations.with_streaming_response.create(
+ endpoint="x",
+ name="name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.retrieve(
+ "id_or_name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Kernel) -> None:
+ response = client.telemetry.destinations.with_raw_response.retrieve(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Kernel) -> None:
+ with client.telemetry.destinations.with_streaming_response.retrieve(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.telemetry.destinations.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.update(
+ id_or_name="id_or_name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.update(
+ id_or_name="id_or_name",
+ description="description",
+ endpoint="x",
+ headers={"foo": "string"},
+ name="name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Kernel) -> None:
+ response = client.telemetry.destinations.with_raw_response.update(
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Kernel) -> None:
+ with client.telemetry.destinations.with_streaming_response.update(
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_update(self, client: Kernel) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
+ client.telemetry.destinations.with_raw_response.update(
+ id_or_name="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.list()
+ assert_matches_type(SyncOffsetPagination[OtlpDestination], destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.list(
+ limit=1,
+ name="name",
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(SyncOffsetPagination[OtlpDestination], destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Kernel) -> None:
+ response = client.telemetry.destinations.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = response.parse()
+ assert_matches_type(SyncOffsetPagination[OtlpDestination], destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Kernel) -> None:
+ with client.telemetry.destinations.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = response.parse()
+ assert_matches_type(SyncOffsetPagination[OtlpDestination], destination, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: Kernel) -> None:
+ destination = client.telemetry.destinations.delete(
+ "id_or_name",
+ )
+ assert destination is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Kernel) -> None:
+ response = client.telemetry.destinations.with_raw_response.delete(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = response.parse()
+ assert destination is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Kernel) -> None:
+ with client.telemetry.destinations.with_streaming_response.delete(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = response.parse()
+ assert destination 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.telemetry.destinations.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncDestinations:
+ 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_create(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.create(
+ endpoint="x",
+ name="name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.create(
+ endpoint="x",
+ name="name",
+ description="description",
+ headers={"foo": "string"},
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncKernel) -> None:
+ response = await async_client.telemetry.destinations.with_raw_response.create(
+ endpoint="x",
+ name="name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncKernel) -> None:
+ async with async_client.telemetry.destinations.with_streaming_response.create(
+ endpoint="x",
+ name="name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.retrieve(
+ "id_or_name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None:
+ response = await async_client.telemetry.destinations.with_raw_response.retrieve(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> None:
+ async with async_client.telemetry.destinations.with_streaming_response.retrieve(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, 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_retrieve(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.telemetry.destinations.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.update(
+ id_or_name="id_or_name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.update(
+ id_or_name="id_or_name",
+ description="description",
+ endpoint="x",
+ headers={"foo": "string"},
+ name="name",
+ )
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncKernel) -> None:
+ response = await async_client.telemetry.destinations.with_raw_response.update(
+ id_or_name="id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncKernel) -> None:
+ async with async_client.telemetry.destinations.with_streaming_response.update(
+ id_or_name="id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = await response.parse()
+ assert_matches_type(OtlpDestination, destination, 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_update(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.telemetry.destinations.with_raw_response.update(
+ id_or_name="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.list()
+ assert_matches_type(AsyncOffsetPagination[OtlpDestination], destination, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.list(
+ limit=1,
+ name="name",
+ offset=0,
+ query="query",
+ )
+ assert_matches_type(AsyncOffsetPagination[OtlpDestination], destination, 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.telemetry.destinations.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[OtlpDestination], destination, 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.telemetry.destinations.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = await response.parse()
+ assert_matches_type(AsyncOffsetPagination[OtlpDestination], destination, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncKernel) -> None:
+ destination = await async_client.telemetry.destinations.delete(
+ "id_or_name",
+ )
+ assert destination 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.telemetry.destinations.with_raw_response.delete(
+ "id_or_name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ destination = await response.parse()
+ assert destination 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.telemetry.destinations.with_streaming_response.delete(
+ "id_or_name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ destination = await response.parse()
+ assert destination 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.telemetry.destinations.with_raw_response.delete(
+ "",
+ )
From db06ca8018bf8baf7ff65ddf917415cd66da726a Mon Sep 17 00:00:00 2001
From: "kernel-internal[bot]"
<260533166+kernel-internal[bot]@users.noreply.github.com>
Date: Fri, 14 Aug 2026 18:12:22 +0000
Subject: [PATCH 4/4] release: 0.91.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 9 +++++++++
pyproject.toml | 2 +-
src/kernel/_version.py | 2 +-
4 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index f2863752..29c535cb 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.90.0"
+ ".": "0.91.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6add314..4b641f09 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+## [0.91.0](https://github.com/kernel/kernel-python-sdk/compare/v0.90.0...v0.91.0) (2026-08-14)
+
+
+### Features
+
+* Add customer-facing OTLP destination CRUD API ([8479adf](https://github.com/kernel/kernel-python-sdk/commit/8479adff62631c996a4a9129fd8639c16d96b92b))
+* Expose configurable browser memory ([ccf4127](https://github.com/kernel/kernel-python-sdk/commit/ccf4127272adc749f0da75b7e2458e4a9e183423))
+* Require a name on credential providers and backfill unnamed rows ([0d5a4c2](https://github.com/kernel/kernel-python-sdk/commit/0d5a4c29be2e2bc0a216436cbda4bc8602784d02))
+
## [0.90.0](https://github.com/kernel/kernel-python-sdk/compare/v0.89.0...v0.90.0) (2026-08-12)
diff --git a/pyproject.toml b/pyproject.toml
index 521e38c9..1c078ba3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "kernel"
-version = "0.90.0"
+version = "0.91.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 8933bc49..06739f24 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.90.0" # x-release-please-version
+__version__ = "0.91.0" # x-release-please-version