diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d486cde7..ca93fdaa 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.96.0" + ".": "0.97.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f4fdbc82..01df1612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.97.0](https://github.com/kernel/kernel-python-sdk/compare/v0.96.0...v0.97.0) (2026-08-31) + + +### Features + +* Expose captcha task and challenge outcomes ([42c33c6](https://github.com/kernel/kernel-python-sdk/commit/42c33c6e08ed6018cd93ddf6704ea70b206ca159)) +* Unify managed auth reauth eligibility and blockers ([8e11fb2](https://github.com/kernel/kernel-python-sdk/commit/8e11fb2ab0b6ddddcb28af0d503489b94cfba1f0)) + ## [0.96.0](https://github.com/kernel/kernel-python-sdk/compare/v0.95.0...v0.96.0) (2026-08-27) diff --git a/api.md b/api.md index d5cdf897..6cd72abc 100644 --- a/api.md +++ b/api.md @@ -152,7 +152,9 @@ Types: from kernel.types.browsers import ( BrowserAPICallEvent, BrowserCallStack, + BrowserCaptchaChallengeResultEvent, BrowserCaptchaSolveResultEvent, + BrowserCaptchaSolveStartedEvent, BrowserCdpCommandEvent, BrowserCdpCommandMethod, BrowserCdpConnectEvent, diff --git a/pyproject.toml b/pyproject.toml index 32520ba7..2400b318 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.96.0" +version = "0.97.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 428f45a2..f1214100 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.96.0" # x-release-please-version +__version__ = "0.97.0" # x-release-please-version diff --git a/src/kernel/types/auth/managed_auth.py b/src/kernel/types/auth/managed_auth.py index 7c4f9668..a358197e 100644 --- a/src/kernel/types/auth/managed_auth.py +++ b/src/kernel/types/auth/managed_auth.py @@ -377,6 +377,7 @@ class ManagedAuth(BaseModel): "has_credential", "viable_plans_found", "no_requirements_recorded", + "totp_reauth_allowed", "requirements_satisfiable", "no_prior_successful_login", "no_credential", @@ -400,8 +401,10 @@ class ManagedAuth(BaseModel): - `viable_plans_found` — at least one stored login plan can be replayed - `no_requirements_recorded` — no recorded credential requirements to fail against - - `requirements_satisfiable` — recorded requirements can be met by the attached - credential + - `totp_reauth_allowed` — TOTP is the only recorded requirement and is safe to + attempt automatically + - `requirements_satisfiable` — recorded requirements contain no recognized + blocker Negative values (a human must complete the login flow): diff --git a/src/kernel/types/browsers/__init__.py b/src/kernel/types/browsers/__init__.py index a6d6eb74..927f8a40 100644 --- a/src/kernel/types/browsers/__init__.py +++ b/src/kernel/types/browsers/__init__.py @@ -87,6 +87,7 @@ from .browser_live_view_disconnect_event import BrowserLiveViewDisconnectEvent as BrowserLiveViewDisconnectEvent from .browser_monitor_disconnected_event import BrowserMonitorDisconnectedEvent as BrowserMonitorDisconnectedEvent from .computer_capture_screenshot_params import ComputerCaptureScreenshotParams as ComputerCaptureScreenshotParams +from .browser_captcha_solve_started_event import BrowserCaptchaSolveStartedEvent as BrowserCaptchaSolveStartedEvent from .browser_telemetry_categories_config import BrowserTelemetryCategoriesConfig as BrowserTelemetryCategoriesConfig from .browser_network_loading_failed_event import BrowserNetworkLoadingFailedEvent as BrowserNetworkLoadingFailedEvent from .browser_telemetry_cdp_control_config import BrowserTelemetryCdpControlConfig as BrowserTelemetryCdpControlConfig @@ -99,6 +100,9 @@ from .computer_set_cursor_visibility_params import ( ComputerSetCursorVisibilityParams as ComputerSetCursorVisibilityParams, ) +from .browser_captcha_challenge_result_event import ( + BrowserCaptchaChallengeResultEvent as BrowserCaptchaChallengeResultEvent, +) from .browser_monitor_reconnect_failed_event import ( BrowserMonitorReconnectFailedEvent as BrowserMonitorReconnectFailedEvent, ) diff --git a/src/kernel/types/browsers/browser_captcha_challenge_result_event.py b/src/kernel/types/browsers/browser_captcha_challenge_result_event.py new file mode 100644 index 00000000..978c49cf --- /dev/null +++ b/src/kernel/types/browsers/browser_captcha_challenge_result_event.py @@ -0,0 +1,85 @@ +# 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 + +__all__ = ["BrowserCaptchaChallengeResultEvent", "Data"] + + +class Data(BaseModel): + """Per-challenge payload. + + This event is emitted once per challenge and determines its overall outcome; captcha_solve_started and captcha_solve_result describe individual tasks and may occur multiple times within the challenge. + """ + + captcha_type: Literal["hcaptcha", "recaptcha_v2", "recaptcha_v3", "turnstile", "geetest", "press_and_hold", "other"] + """Captcha kind. + + Enterprise reCAPTCHA variants are grouped into their version bucket + (recaptcha_v2 or recaptcha_v3), press-and-hold challenges use press_and_hold, + and unlisted kinds use other. + """ + + challenge_id: str + """Opaque identifier shared by events for one visible challenge. + + An image-grid captcha may create multiple task_id values for one challenge_id. + The same value may continue across a page reload when the challenge episode + continues. It does not indicate task ordering or challenge completion. + """ + + duration_ms: float + """ + Wall-clock duration from the challenge appearing to its terminal outcome, + covering every solver attempt in between. + """ + + status: Literal["solved", "failure", "timeout", "abandoned"] + """Terminal outcome of the visible challenge. + + solved: the page observed the challenge clear after a solver attempt. failure: a + terminal solver failure occurred, or all attempts ended while the challenge + remained. timeout: the challenge-level wait budget expired while the challenge + remained. abandoned: observation ended without an attributable terminal + challenge outcome. This includes a dismissed widget or page unload without a + solved signal or terminal solver outcome, and a token appearing while multiple + same-provider challenges are open, because the producer cannot attribute that + token to this visible challenge. A captcha_solve_result with the same + challenge_id may therefore report success while the challenge result reports + abandoned. A solved challenge does not prove the site accepted the token or that + the guarded action succeeded. + """ + + website_host: Optional[str] = None + """Host of the page where the challenge appeared.""" + + website_path: Optional[str] = None + """Path of the page where the challenge appeared. Query string excluded.""" + + +class BrowserCaptchaChallengeResultEvent(BaseModel): + """A visible captcha challenge reached a terminal outcome.""" + + category: Literal["captcha"] + + data: Data + """Per-challenge payload. + + This event is emitted once per challenge and determines its overall outcome; + captcha_solve_started and captcha_solve_result describe individual tasks and may + occur multiple times within the challenge. + """ + + source: BrowserEventSource + """Provenance metadata identifying which producer emitted the event.""" + + ts: int + """Event timestamp in Unix microseconds.""" + + type: Literal["captcha_challenge_result"] + + truncated: Optional[bool] = None + """True if the data field was truncated due to size limits.""" diff --git a/src/kernel/types/browsers/browser_captcha_solve_result_event.py b/src/kernel/types/browsers/browser_captcha_solve_result_event.py index fb8476d4..2e023cc5 100644 --- a/src/kernel/types/browsers/browser_captcha_solve_result_event.py +++ b/src/kernel/types/browsers/browser_captcha_solve_result_event.py @@ -10,15 +10,20 @@ class Data(BaseModel): - captcha_type: Literal["hcaptcha", "recaptcha_v2", "recaptcha_v3", "turnstile", "geetest", "other"] - """Captcha vendor family. + captcha_type: Literal["hcaptcha", "recaptcha_v2", "recaptcha_v3", "turnstile", "geetest", "press_and_hold", "other"] + """Captcha kind. - Provider-specific task names are normalized into this set; anything not covered - is reported as other. + Enterprise reCAPTCHA variants are grouped into their version bucket + (recaptcha_v2 or recaptcha_v3), press-and-hold challenges use press_and_hold, + and unlisted kinds use other. """ duration_ms: float - """Wall-clock duration from solve start to terminal outcome.""" + """Wall-clock duration from solve start to terminal outcome. + + Authoritative solve timing; do not derive it from the gap to a + captcha_solve_started event, whose delivery and ordering are not guaranteed. + """ status: Literal["success", "failure", "timeout", "abandoned"] """Terminal outcome. @@ -28,6 +33,14 @@ class Data(BaseModel): budget. abandoned: caller cancelled or the page navigated away mid-solve. """ + challenge_id: Optional[str] = None + """Opaque identifier shared by events for one visible challenge. + + An image-grid captcha may create multiple task_id values for one challenge_id. + The same value may continue across a page reload when the challenge episode + continues. It does not indicate task ordering or challenge completion. + """ + error_code: Optional[str] = None """Solver-specific error code on failure (e.g. @@ -35,7 +48,7 @@ class Data(BaseModel): """ task_id: Optional[str] = None - """Solver-assigned identifier. Opaque, useful for support cross-references.""" + """Opaque identifier shared with the matching captcha_solve_started.""" website_host: Optional[str] = None """Host of the page where the captcha was solved.""" diff --git a/src/kernel/types/browsers/browser_captcha_solve_started_event.py b/src/kernel/types/browsers/browser_captcha_solve_started_event.py new file mode 100644 index 00000000..546c1c38 --- /dev/null +++ b/src/kernel/types/browsers/browser_captcha_solve_started_event.py @@ -0,0 +1,70 @@ +# 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 + +__all__ = ["BrowserCaptchaSolveStartedEvent", "Data"] + + +class Data(BaseModel): + """Per-task payload. + + A visible challenge may create multiple tasks. When present, task_id correlates this event with a captcha_solve_result, while challenge_id groups tasks from the same challenge. Events may arrive out of order or be absent, so their arrival does not indicate current solve state. + """ + + captcha_type: Literal["hcaptcha", "recaptcha_v2", "recaptcha_v3", "turnstile", "geetest", "press_and_hold", "other"] + """Captcha kind. + + Enterprise reCAPTCHA variants are grouped into their version bucket + (recaptcha_v2 or recaptcha_v3), press-and-hold challenges use press_and_hold, + and unlisted kinds use other. + """ + + challenge_id: Optional[str] = None + """Opaque identifier shared by events for one visible challenge. + + An image-grid captcha may create multiple task_id values for one challenge_id. + The same value may continue across a page reload when the challenge episode + continues. It does not indicate task ordering or challenge completion. + """ + + task_id: Optional[str] = None + """Opaque identifier shared with the matching captcha_solve_result.""" + + website_host: Optional[str] = None + """Host of the page where the captcha is being solved. + + May be empty for solver tasks that carry no page URL. + """ + + website_path: Optional[str] = None + """Path of the page where the captcha is being solved. Query string excluded.""" + + +class BrowserCaptchaSolveStartedEvent(BaseModel): + """A captcha solver accepted a task.""" + + category: Literal["captcha"] + + data: Data + """Per-task payload. + + A visible challenge may create multiple tasks. When present, task_id correlates + this event with a captcha_solve_result, while challenge_id groups tasks from the + same challenge. Events may arrive out of order or be absent, so their arrival + does not indicate current solve state. + """ + + source: BrowserEventSource + """Provenance metadata identifying which producer emitted the event.""" + + ts: int + """Event timestamp in Unix microseconds.""" + + type: Literal["captcha_solve_started"] + + truncated: Optional[bool] = None + """True if the data field was truncated due to size limits.""" diff --git a/src/kernel/types/browsers/browser_telemetry_categories_config.py b/src/kernel/types/browsers/browser_telemetry_categories_config.py index 8b1f811a..062b360a 100644 --- a/src/kernel/types/browsers/browser_telemetry_categories_config.py +++ b/src/kernel/types/browsers/browser_telemetry_categories_config.py @@ -16,7 +16,7 @@ class BrowserTelemetryCategoriesConfig(BaseModel): """ captcha: Optional[BrowserTelemetryCategoryConfig] = None - """Captcha solve attempt outcomes. On by default.""" + """Captcha solver tasks and visible challenge outcomes. On by default.""" connection: Optional[BrowserTelemetryCategoryConfig] = None """Client attach/detach lifecycle for the CDP proxy and live view. On by default.""" diff --git a/src/kernel/types/browsers/browser_telemetry_categories_config_param.py b/src/kernel/types/browsers/browser_telemetry_categories_config_param.py index e707230f..d84d79d8 100644 --- a/src/kernel/types/browsers/browser_telemetry_categories_config_param.py +++ b/src/kernel/types/browsers/browser_telemetry_categories_config_param.py @@ -17,7 +17,7 @@ class BrowserTelemetryCategoriesConfigParam(TypedDict, total=False): """ captcha: BrowserTelemetryCategoryConfigParam - """Captcha solve attempt outcomes. On by default.""" + """Captcha solver tasks and visible challenge outcomes. On by default.""" connection: BrowserTelemetryCategoryConfigParam """Client attach/detach lifecycle for the CDP proxy and live view. On by default.""" diff --git a/src/kernel/types/browsers/browser_telemetry_event.py b/src/kernel/types/browsers/browser_telemetry_event.py index 2ce872f4..d6c58a00 100644 --- a/src/kernel/types/browsers/browser_telemetry_event.py +++ b/src/kernel/types/browsers/browser_telemetry_event.py @@ -35,9 +35,11 @@ from .browser_captcha_solve_result_event import BrowserCaptchaSolveResultEvent from .browser_live_view_disconnect_event import BrowserLiveViewDisconnectEvent from .browser_monitor_disconnected_event import BrowserMonitorDisconnectedEvent +from .browser_captcha_solve_started_event import BrowserCaptchaSolveStartedEvent from .browser_network_loading_failed_event import BrowserNetworkLoadingFailedEvent from .browser_page_dom_content_loaded_event import BrowserPageDomContentLoadedEvent from .browser_page_navigation_settled_event import BrowserPageNavigationSettledEvent +from .browser_captcha_challenge_result_event import BrowserCaptchaChallengeResultEvent from .browser_monitor_reconnect_failed_event import BrowserMonitorReconnectFailedEvent from .browser_interaction_scroll_settled_event import BrowserInteractionScrollSettledEvent @@ -76,7 +78,9 @@ BrowserCdpDisconnectEvent, BrowserLiveViewConnectEvent, BrowserLiveViewDisconnectEvent, + BrowserCaptchaSolveStartedEvent, BrowserCaptchaSolveResultEvent, + BrowserCaptchaChallengeResultEvent, BrowserSystemOomKillEvent, BrowserServiceCrashedEvent, ],