diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0114ce37..c486f6de 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.92.0" + ".": "0.93.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index fb983168..1283a424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [0.93.0](https://github.com/kernel/kernel-python-sdk/compare/v0.92.0...v0.93.0) (2026-08-19) + + +### Features + +* Add proxy_error to browser telemetry event schema ([5973ab6](https://github.com/kernel/kernel-python-sdk/commit/5973ab6c766ee31929a523e3ea21fb000bc6879a)) +* Bind managed auth submissions to interactions ([63fb060](https://github.com/kernel/kernel-python-sdk/commit/63fb0606910b17a99f7a584e425fbd77e2d562e5)) +* Harden canonical managed auth interactions ([3522fcb](https://github.com/kernel/kernel-python-sdk/commit/3522fcb9e70854ad2900585f5b37222e7f0e1b26)) + ## [0.92.0](https://github.com/kernel/kernel-python-sdk/compare/v0.91.0...v0.92.0) (2026-08-17) diff --git a/api.md b/api.md index fda90ce0..6f8c4256 100644 --- a/api.md +++ b/api.md @@ -146,6 +146,7 @@ from kernel.types.browsers import ( BrowserPageNavigationEvent, BrowserPageNavigationSettledEvent, BrowserPageTabOpenedEvent, + BrowserProxyErrorEvent, BrowserServiceCrashedEvent, BrowserSystemOomKillEvent, BrowserTelemetryCategoriesConfig, diff --git a/pyproject.toml b/pyproject.toml index dbb578c4..8f8dab0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.92.0" +version = "0.93.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 ce129655..2fb26cce 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.92.0" # x-release-please-version +__version__ = "0.93.0" # x-release-please-version diff --git a/src/kernel/resources/auth/connections.py b/src/kernel/resources/auth/connections.py index 56785ea2..ccc3d890 100644 --- a/src/kernel/resources/auth/connections.py +++ b/src/kernel/resources/auth/connections.py @@ -530,6 +530,7 @@ def submit( *, field_values: Dict[str, str] | Omit = omit, fields: Dict[str, str] | Omit = omit, + interaction_id: str | Omit = omit, mfa_option_id: str | Omit = omit, selected_choice_id: str | Omit = omit, sign_in_option_id: str | Omit = omit, @@ -552,6 +553,9 @@ def submit( fields: Map of field name to value + interaction_id: Opaque interaction ID returned with canonical fields and choices. Required for + canonical submissions. + mfa_option_id: The MFA method type to select (when mfa_options were returned) selected_choice_id: Canonical choice ID selected by the user. @@ -580,6 +584,7 @@ def submit( { "field_values": field_values, "fields": fields, + "interaction_id": interaction_id, "mfa_option_id": mfa_option_id, "selected_choice_id": selected_choice_id, "sign_in_option_id": sign_in_option_id, @@ -1143,6 +1148,7 @@ async def submit( *, field_values: Dict[str, str] | Omit = omit, fields: Dict[str, str] | Omit = omit, + interaction_id: str | Omit = omit, mfa_option_id: str | Omit = omit, selected_choice_id: str | Omit = omit, sign_in_option_id: str | Omit = omit, @@ -1165,6 +1171,9 @@ async def submit( fields: Map of field name to value + interaction_id: Opaque interaction ID returned with canonical fields and choices. Required for + canonical submissions. + mfa_option_id: The MFA method type to select (when mfa_options were returned) selected_choice_id: Canonical choice ID selected by the user. @@ -1193,6 +1202,7 @@ async def submit( { "field_values": field_values, "fields": fields, + "interaction_id": interaction_id, "mfa_option_id": mfa_option_id, "selected_choice_id": selected_choice_id, "sign_in_option_id": sign_in_option_id, diff --git a/src/kernel/types/auth/connection_follow_response.py b/src/kernel/types/auth/connection_follow_response.py index c411c1ba..ff6dcf0b 100644 --- a/src/kernel/types/auth/connection_follow_response.py +++ b/src/kernel/types/auth/connection_follow_response.py @@ -97,6 +97,9 @@ class ManagedAuthStateEventField(BaseModel): id: str """Stable field identifier for canonical submit.""" + reason: Literal["missing", "rejected"] + """Why the field requires user input.""" + ref: str """Credential reference name to store the submitted value under.""" @@ -112,12 +115,6 @@ class ManagedAuthStateEventField(BaseModel): observed_selector: Optional[str] = None """Selector for the visible field, when available.""" - replace_existing: Optional[bool] = None - """ - Whether the submitted value must replace an existing credential after explicit - rejection. - """ - required: Optional[bool] = None """Whether this field is required.""" @@ -224,6 +221,13 @@ class ManagedAuthStateEvent(BaseModel): hosted_url: Optional[str] = None """URL to redirect user to for hosted login.""" + interaction_id: Optional[str] = None + """Opaque identifier for the current canonical interaction. + + Required when submitting fields or choices and changes for each new actionable + pause. + """ + live_view_url: Optional[str] = None """Browser live view URL for debugging.""" diff --git a/src/kernel/types/auth/connection_submit_params.py b/src/kernel/types/auth/connection_submit_params.py index 19861ed8..04421fef 100644 --- a/src/kernel/types/auth/connection_submit_params.py +++ b/src/kernel/types/auth/connection_submit_params.py @@ -15,6 +15,12 @@ class ConnectionSubmitParams(TypedDict, total=False): fields: Dict[str, str] """Map of field name to value""" + interaction_id: str + """Opaque interaction ID returned with canonical fields and choices. + + Required for canonical submissions. + """ + mfa_option_id: str """The MFA method type to select (when mfa_options were returned)""" diff --git a/src/kernel/types/auth/managed_auth.py b/src/kernel/types/auth/managed_auth.py index ea5be1c6..23e45d6b 100644 --- a/src/kernel/types/auth/managed_auth.py +++ b/src/kernel/types/auth/managed_auth.py @@ -206,6 +206,9 @@ class Field(BaseModel): id: str """Stable field identifier for canonical submit.""" + reason: Literal["missing", "rejected"] + """Why the field requires user input.""" + ref: str """Credential reference name to store the submitted value under.""" @@ -221,12 +224,6 @@ class Field(BaseModel): observed_selector: Optional[str] = None """Selector for the visible field, when available.""" - replace_existing: Optional[bool] = None - """ - Whether the submitted value must replace an existing credential after explicit - rejection. - """ - required: Optional[bool] = None """Whether this field is required.""" @@ -504,6 +501,13 @@ class ManagedAuth(BaseModel): hosted_url: Optional[str] = None """URL to redirect user to for hosted login (present when flow in progress)""" + interaction_id: Optional[str] = None + """Opaque identifier for the current canonical interaction. + + Required when submitting fields or choices and changes for each new actionable + pause. + """ + last_auth_at: Optional[datetime] = None """Deprecated alias for `last_auth_check_at`. diff --git a/src/kernel/types/browsers/__init__.py b/src/kernel/types/browsers/__init__.py index bf9d861a..8ed451a0 100644 --- a/src/kernel/types/browsers/__init__.py +++ b/src/kernel/types/browsers/__init__.py @@ -42,6 +42,7 @@ from .browser_telemetry_config import BrowserTelemetryConfig as BrowserTelemetryConfig from .browser_cdp_connect_event import BrowserCdpConnectEvent as BrowserCdpConnectEvent from .browser_console_log_event import BrowserConsoleLogEvent as BrowserConsoleLogEvent +from .browser_proxy_error_event import BrowserProxyErrorEvent as BrowserProxyErrorEvent from .computer_press_key_params import ComputerPressKeyParams as ComputerPressKeyParams from .computer_type_text_params import ComputerTypeTextParams as ComputerTypeTextParams from .f_create_directory_params import FCreateDirectoryParams as FCreateDirectoryParams diff --git a/src/kernel/types/browsers/browser_proxy_error_event.py b/src/kernel/types/browsers/browser_proxy_error_event.py new file mode 100644 index 00000000..6f52d3bb --- /dev/null +++ b/src/kernel/types/browsers/browser_proxy_error_event.py @@ -0,0 +1,73 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .browser_event_source import BrowserEventSource +from .browser_event_context import BrowserEventContext + +__all__ = ["BrowserProxyErrorEvent", "Data"] + + +class Data(BrowserEventContext): + """Browser event context stamped by the browser monitor onto all CDP-sourced events. + + Identifies the target, frame, and navigation epoch in which the event occurred. + """ + + code: Literal[ + "destination_blocked", + "provider_blacklisted", + "provider_unreachable", + "proxy_unavailable", + "upstream_timeout", + "upstream_dns_failure", + "upstream_connect_failed", + ] + """ + Proxy-layer error code: the X-Kernel-Proxy-Error response header value from a + branded 5xx error page served by the metro egress host-proxy. Values mirror what + the proxy emits: destination_blocked, provider_blacklisted, + provider_unreachable, proxy_unavailable, upstream_timeout, upstream_dns_failure, + upstream_connect_failed. Unknown header values are dropped. + """ + + request_id: str + """CDP request identifier matching the originating request.""" + + status: int + """HTTP response status of the branded error page (502).""" + + method: Optional[str] = None + """HTTP method of the failed request, when known.""" + + resource_type: Optional[str] = None + """CDP Network.ResourceType for the request, when known.""" + + +class BrowserProxyErrorEvent(BaseModel): + """A branded proxy-layer failure observed by the browser. + + Emitted when the metro egress host-proxy serves a branded 5xx error page whose response carries the X-Kernel-Proxy-Error header. Low-volume and carries a typed code. Its value is per-session and per-URL attribution for sessions that already capture the network stream: proxy failures are only observable while the CDP network collector is running, so this is an opt-in refinement of the raw network events rather than a default-on alerting signal. + """ + + category: Literal["network"] + + source: BrowserEventSource + """Provenance metadata identifying which producer emitted the event.""" + + ts: int + """Event timestamp in Unix microseconds.""" + + type: Literal["proxy_error"] + + data: Optional[Data] = None + """Browser event context stamped by the browser monitor onto all CDP-sourced + events. + + Identifies the target, frame, and navigation epoch in which the event occurred. + """ + + truncated: Optional[bool] = None + """True if the data field was truncated due to size limits.""" diff --git a/src/kernel/types/browsers/browser_telemetry_event.py b/src/kernel/types/browsers/browser_telemetry_event.py index d27537e8..d57b92d2 100644 --- a/src/kernel/types/browsers/browser_telemetry_event.py +++ b/src/kernel/types/browsers/browser_telemetry_event.py @@ -11,6 +11,7 @@ from .browser_page_load_event import BrowserPageLoadEvent from .browser_cdp_connect_event import BrowserCdpConnectEvent from .browser_console_log_event import BrowserConsoleLogEvent +from .browser_proxy_error_event import BrowserProxyErrorEvent from .browser_network_idle_event import BrowserNetworkIdleEvent from .browser_console_error_event import BrowserConsoleErrorEvent from .browser_cdp_disconnect_event import BrowserCdpDisconnectEvent @@ -47,6 +48,7 @@ BrowserNetworkResponseEvent, BrowserNetworkLoadingFailedEvent, BrowserNetworkIdleEvent, + BrowserProxyErrorEvent, BrowserPageNavigationEvent, BrowserPageDomContentLoadedEvent, BrowserPageLoadEvent, diff --git a/tests/api_resources/auth/test_connections.py b/tests/api_resources/auth/test_connections.py index f026a2cd..f3d4a05c 100644 --- a/tests/api_resources/auth/test_connections.py +++ b/tests/api_resources/auth/test_connections.py @@ -556,6 +556,7 @@ def test_method_submit_with_all_params(self, client: Kernel) -> None: "email": "user@example.com", "password": "secret", }, + interaction_id="mai_abc123xyz", mfa_option_id="sms", selected_choice_id="google", sign_in_option_id="work-account", @@ -1190,6 +1191,7 @@ async def test_method_submit_with_all_params(self, async_client: AsyncKernel) -> "email": "user@example.com", "password": "secret", }, + interaction_id="mai_abc123xyz", mfa_option_id="sms", selected_choice_id="google", sign_in_option_id="work-account",