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