From 57af2e181b716145ff3e11a0d74c04dd332f9e35 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:00:22 +0000 Subject: [PATCH 1/8] refactor(browser): remove persistence option UI --- .stats.yml | 6 +- README.md | 18 +++--- src/kernel/resources/browsers/browsers.py | 61 +++++++++++------- src/kernel/types/browser_create_params.py | 11 ++-- src/kernel/types/browser_create_response.py | 2 +- src/kernel/types/browser_list_response.py | 2 +- src/kernel/types/browser_persistence.py | 2 +- src/kernel/types/browser_persistence_param.py | 2 +- .../types/browser_pool_acquire_response.py | 2 +- src/kernel/types/browser_retrieve_response.py | 2 +- tests/api_resources/test_browsers.py | 64 +++++++++++-------- 11 files changed, 96 insertions(+), 76 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7792bb0..a0095f1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 74 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-003e9afa15f0765009d2c7d34e8eb62268d818e628e3c84361b21138e30cc423.yml -openapi_spec_hash: c1b8309f60385bf2b02d245363ca47c1 -config_hash: a4124701ae0a474e580d7416adbcfb00 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-cbef7e4fef29ad40af5c767aceb762ee68811c2287f255c05d2ee44a9a9247a2.yml +openapi_spec_hash: 467e61e072773ec9f2d49c7dd3de8c2f +config_hash: 6dbe88d2ba9df1ec46cedbfdb7d00000 diff --git a/README.md b/README.md index d5905ea..6d1dd19 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ client = Kernel( ) browser = client.browsers.create( - persistence={"id": "browser-for-user-1234"}, + stealth=True, ) print(browser.session_id) ``` @@ -63,7 +63,7 @@ client = AsyncKernel( async def main() -> None: browser = await client.browsers.create( - persistence={"id": "browser-for-user-1234"}, + stealth=True, ) print(browser.session_id) @@ -99,7 +99,7 @@ async def main() -> None: http_client=DefaultAioHttpClient(), ) as client: browser = await client.browsers.create( - persistence={"id": "browser-for-user-1234"}, + stealth=True, ) print(browser.session_id) @@ -242,7 +242,7 @@ client = Kernel() try: client.browsers.create( - persistence={"id": "browser-for-user-1234"}, + stealth=True, ) except kernel.APIConnectionError as e: print("The server could not be reached") @@ -287,7 +287,7 @@ client = Kernel( # Or, configure per-request: client.with_options(max_retries=5).browsers.create( - persistence={"id": "browser-for-user-1234"}, + stealth=True, ) ``` @@ -312,7 +312,7 @@ client = Kernel( # Override per-request: client.with_options(timeout=5.0).browsers.create( - persistence={"id": "browser-for-user-1234"}, + stealth=True, ) ``` @@ -355,9 +355,7 @@ from kernel import Kernel client = Kernel() response = client.browsers.with_raw_response.create( - persistence={ - "id": "browser-for-user-1234" - }, + stealth=True, ) print(response.headers.get('X-My-Header')) @@ -377,7 +375,7 @@ To stream the response body, use `.with_streaming_response` instead, which requi ```python with client.browsers.with_streaming_response.create( - persistence={"id": "browser-for-user-1234"}, + stealth=True, ) as response: print(response.headers.get("X-My-Header")) diff --git a/src/kernel/resources/browsers/browsers.py b/src/kernel/resources/browsers/browsers.py index f41b46a..30888da 100644 --- a/src/kernel/resources/browsers/browsers.py +++ b/src/kernel/resources/browsers/browsers.py @@ -2,6 +2,7 @@ from __future__ import annotations +import typing_extensions from typing import Mapping, Iterable, cast import httpx @@ -161,7 +162,7 @@ def create( kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live view. - persistence: Optional persistence configuration for the browser session. + persistence: DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead. profile: Profile selection for the browser session. Provide either id or name. If specified, the matching profile will be loaded into the browser session. @@ -174,11 +175,10 @@ def create( mechanisms. timeout_seconds: The number of seconds of inactivity before the browser session is terminated. - Only applicable to non-persistent browsers. Activity includes CDP connections - and live view connections. Defaults to 60 seconds. Minimum allowed is 10 - seconds. Maximum allowed is 259200 (72 hours). We check for inactivity every 5 - seconds, so the actual timeout behavior you will see is +/- 5 seconds around the - specified value. + Activity includes CDP connections and live view connections. Defaults to 60 + seconds. Minimum allowed is 10 seconds. Maximum allowed is 259200 (72 hours). We + check for inactivity every 5 seconds, so the actual timeout behavior you will + see is +/- 5 seconds around the specified value. viewport: Initial browser window size in pixels with optional refresh rate. If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport @@ -307,6 +307,7 @@ def list( model=BrowserListResponse, ) + @typing_extensions.deprecated("deprecated") def delete( self, *, @@ -318,8 +319,10 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: - """ - Delete a persistent browser session by its persistent_id. + """DEPRECATED: Use DELETE /browsers/{id} instead. + + Delete a persistent browser + session by its persistent_id. Args: persistent_id: Persistent browser identifier @@ -504,7 +507,7 @@ async def create( kiosk_mode: If true, launches the browser in kiosk mode to hide address bar and tabs in live view. - persistence: Optional persistence configuration for the browser session. + persistence: DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead. profile: Profile selection for the browser session. Provide either id or name. If specified, the matching profile will be loaded into the browser session. @@ -517,11 +520,10 @@ async def create( mechanisms. timeout_seconds: The number of seconds of inactivity before the browser session is terminated. - Only applicable to non-persistent browsers. Activity includes CDP connections - and live view connections. Defaults to 60 seconds. Minimum allowed is 10 - seconds. Maximum allowed is 259200 (72 hours). We check for inactivity every 5 - seconds, so the actual timeout behavior you will see is +/- 5 seconds around the - specified value. + Activity includes CDP connections and live view connections. Defaults to 60 + seconds. Minimum allowed is 10 seconds. Maximum allowed is 259200 (72 hours). We + check for inactivity every 5 seconds, so the actual timeout behavior you will + see is +/- 5 seconds around the specified value. viewport: Initial browser window size in pixels with optional refresh rate. If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport @@ -650,6 +652,7 @@ def list( model=BrowserListResponse, ) + @typing_extensions.deprecated("deprecated") async def delete( self, *, @@ -661,8 +664,10 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: - """ - Delete a persistent browser session by its persistent_id. + """DEPRECATED: Use DELETE /browsers/{id} instead. + + Delete a persistent browser + session by its persistent_id. Args: persistent_id: Persistent browser identifier @@ -784,8 +789,10 @@ def __init__(self, browsers: BrowsersResource) -> None: self.list = to_raw_response_wrapper( browsers.list, ) - self.delete = to_raw_response_wrapper( - browsers.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + browsers.delete, # pyright: ignore[reportDeprecated], + ) ) self.delete_by_id = to_raw_response_wrapper( browsers.delete_by_id, @@ -832,8 +839,10 @@ def __init__(self, browsers: AsyncBrowsersResource) -> None: self.list = async_to_raw_response_wrapper( browsers.list, ) - self.delete = async_to_raw_response_wrapper( - browsers.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + browsers.delete, # pyright: ignore[reportDeprecated], + ) ) self.delete_by_id = async_to_raw_response_wrapper( browsers.delete_by_id, @@ -880,8 +889,10 @@ def __init__(self, browsers: BrowsersResource) -> None: self.list = to_streamed_response_wrapper( browsers.list, ) - self.delete = to_streamed_response_wrapper( - browsers.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + browsers.delete, # pyright: ignore[reportDeprecated], + ) ) self.delete_by_id = to_streamed_response_wrapper( browsers.delete_by_id, @@ -928,8 +939,10 @@ def __init__(self, browsers: AsyncBrowsersResource) -> None: self.list = async_to_streamed_response_wrapper( browsers.list, ) - self.delete = async_to_streamed_response_wrapper( - browsers.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + browsers.delete, # pyright: ignore[reportDeprecated], + ) ) self.delete_by_id = async_to_streamed_response_wrapper( browsers.delete_by_id, diff --git a/src/kernel/types/browser_create_params.py b/src/kernel/types/browser_create_params.py index d1ff790..4a5f479 100644 --- a/src/kernel/types/browser_create_params.py +++ b/src/kernel/types/browser_create_params.py @@ -36,7 +36,7 @@ class BrowserCreateParams(TypedDict, total=False): """ persistence: BrowserPersistenceParam - """Optional persistence configuration for the browser session.""" + """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead.""" profile: BrowserProfile """Profile selection for the browser session. @@ -60,11 +60,10 @@ class BrowserCreateParams(TypedDict, total=False): timeout_seconds: int """The number of seconds of inactivity before the browser session is terminated. - Only applicable to non-persistent browsers. Activity includes CDP connections - and live view connections. Defaults to 60 seconds. Minimum allowed is 10 - seconds. Maximum allowed is 259200 (72 hours). We check for inactivity every 5 - seconds, so the actual timeout behavior you will see is +/- 5 seconds around the - specified value. + Activity includes CDP connections and live view connections. Defaults to 60 + seconds. Minimum allowed is 10 seconds. Maximum allowed is 259200 (72 hours). We + check for inactivity every 5 seconds, so the actual timeout behavior you will + see is +/- 5 seconds around the specified value. """ viewport: BrowserViewport diff --git a/src/kernel/types/browser_create_response.py b/src/kernel/types/browser_create_response.py index 0a5f33d..344549e 100644 --- a/src/kernel/types/browser_create_response.py +++ b/src/kernel/types/browser_create_response.py @@ -43,7 +43,7 @@ class BrowserCreateResponse(BaseModel): """Whether the browser session is running in kiosk mode.""" persistence: Optional[BrowserPersistence] = None - """Optional persistence configuration for the browser session.""" + """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead.""" profile: Optional[Profile] = None """Browser profile metadata.""" diff --git a/src/kernel/types/browser_list_response.py b/src/kernel/types/browser_list_response.py index d639729..cfa2ce5 100644 --- a/src/kernel/types/browser_list_response.py +++ b/src/kernel/types/browser_list_response.py @@ -43,7 +43,7 @@ class BrowserListResponse(BaseModel): """Whether the browser session is running in kiosk mode.""" persistence: Optional[BrowserPersistence] = None - """Optional persistence configuration for the browser session.""" + """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead.""" profile: Optional[Profile] = None """Browser profile metadata.""" diff --git a/src/kernel/types/browser_persistence.py b/src/kernel/types/browser_persistence.py index 9c6bfc7..5c362ee 100644 --- a/src/kernel/types/browser_persistence.py +++ b/src/kernel/types/browser_persistence.py @@ -7,4 +7,4 @@ class BrowserPersistence(BaseModel): id: str - """Unique identifier for the persistent browser session.""" + """DEPRECATED: Unique identifier for the persistent browser session.""" diff --git a/src/kernel/types/browser_persistence_param.py b/src/kernel/types/browser_persistence_param.py index b483291..bbd9e48 100644 --- a/src/kernel/types/browser_persistence_param.py +++ b/src/kernel/types/browser_persistence_param.py @@ -9,4 +9,4 @@ class BrowserPersistenceParam(TypedDict, total=False): id: Required[str] - """Unique identifier for the persistent browser session.""" + """DEPRECATED: Unique identifier for the persistent browser session.""" diff --git a/src/kernel/types/browser_pool_acquire_response.py b/src/kernel/types/browser_pool_acquire_response.py index 76ad037..1bb69e8 100644 --- a/src/kernel/types/browser_pool_acquire_response.py +++ b/src/kernel/types/browser_pool_acquire_response.py @@ -43,7 +43,7 @@ class BrowserPoolAcquireResponse(BaseModel): """Whether the browser session is running in kiosk mode.""" persistence: Optional[BrowserPersistence] = None - """Optional persistence configuration for the browser session.""" + """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead.""" profile: Optional[Profile] = None """Browser profile metadata.""" diff --git a/src/kernel/types/browser_retrieve_response.py b/src/kernel/types/browser_retrieve_response.py index 111149b..2b49d65 100644 --- a/src/kernel/types/browser_retrieve_response.py +++ b/src/kernel/types/browser_retrieve_response.py @@ -43,7 +43,7 @@ class BrowserRetrieveResponse(BaseModel): """Whether the browser session is running in kiosk mode.""" persistence: Optional[BrowserPersistence] = None - """Optional persistence configuration for the browser session.""" + """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead.""" profile: Optional[Profile] = None """Browser profile metadata.""" diff --git a/tests/api_resources/test_browsers.py b/tests/api_resources/test_browsers.py index c87fc3d..a766656 100644 --- a/tests/api_resources/test_browsers.py +++ b/tests/api_resources/test_browsers.py @@ -16,6 +16,8 @@ ) from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -163,17 +165,20 @@ def test_streaming_response_list(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_delete(self, client: Kernel) -> None: - browser = client.browsers.delete( - persistent_id="persistent_id", - ) + with pytest.warns(DeprecationWarning): + browser = client.browsers.delete( + persistent_id="persistent_id", + ) + assert browser is None @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_delete(self, client: Kernel) -> None: - response = client.browsers.with_raw_response.delete( - persistent_id="persistent_id", - ) + with pytest.warns(DeprecationWarning): + response = client.browsers.with_raw_response.delete( + persistent_id="persistent_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -183,14 +188,15 @@ def test_raw_response_delete(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_delete(self, client: Kernel) -> None: - with client.browsers.with_streaming_response.delete( - persistent_id="persistent_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.browsers.with_streaming_response.delete( + persistent_id="persistent_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - browser = response.parse() - assert browser is None + browser = response.parse() + assert browser is None assert cast(Any, response.is_closed) is True @@ -449,17 +455,20 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_delete(self, async_client: AsyncKernel) -> None: - browser = await async_client.browsers.delete( - persistent_id="persistent_id", - ) + with pytest.warns(DeprecationWarning): + browser = await async_client.browsers.delete( + persistent_id="persistent_id", + ) + assert browser is None @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_delete(self, async_client: AsyncKernel) -> None: - response = await async_client.browsers.with_raw_response.delete( - persistent_id="persistent_id", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.browsers.with_raw_response.delete( + persistent_id="persistent_id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -469,14 +478,15 @@ async def test_raw_response_delete(self, async_client: AsyncKernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_delete(self, async_client: AsyncKernel) -> None: - async with async_client.browsers.with_streaming_response.delete( - persistent_id="persistent_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - browser = await response.parse() - assert browser is None + with pytest.warns(DeprecationWarning): + async with async_client.browsers.with_streaming_response.delete( + persistent_id="persistent_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + browser = await response.parse() + assert browser is None assert cast(Any, response.is_closed) is True From 2ba9e5740d0b12605de3b48af06658b8db47ba11 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:56:08 +0000 Subject: [PATCH 2/8] feat: [wip] Browser pools polish pass --- .stats.yml | 4 +- src/kernel/resources/browser_pools.py | 44 +++++++++---------- src/kernel/resources/browsers/browsers.py | 20 ++++----- src/kernel/types/browser_create_params.py | 2 +- src/kernel/types/browser_create_response.py | 2 +- src/kernel/types/browser_list_response.py | 2 +- .../types/browser_pool_acquire_response.py | 2 +- .../types/browser_pool_create_params.py | 2 +- src/kernel/types/browser_pool_request.py | 2 +- .../types/browser_pool_update_params.py | 4 +- src/kernel/types/browser_retrieve_response.py | 2 +- 11 files changed, 43 insertions(+), 43 deletions(-) diff --git a/.stats.yml b/.stats.yml index a0095f1..44c807a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 74 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-cbef7e4fef29ad40af5c767aceb762ee68811c2287f255c05d2ee44a9a9247a2.yml -openapi_spec_hash: 467e61e072773ec9f2d49c7dd3de8c2f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-3a68acd8c46e121c66be5b4c30bb4e962967840ca0f31070905baa39635fbc2d.yml +openapi_spec_hash: 9453963fbb01de3e0afb462b16cdf115 config_hash: 6dbe88d2ba9df1ec46cedbfdb7d00000 diff --git a/src/kernel/resources/browser_pools.py b/src/kernel/resources/browser_pools.py index d085d51..8c480ed 100644 --- a/src/kernel/resources/browser_pools.py +++ b/src/kernel/resources/browser_pools.py @@ -106,11 +106,11 @@ def create( are destroyed. Defaults to 600 seconds if not specified viewport: Initial browser window size in pixels with optional refresh rate. If omitted, - image defaults apply (commonly 1024x768@60). Only specific viewport - configurations are supported. The server will reject unsupported combinations. - Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, - 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will - be automatically determined from the width and height if they match a supported + image defaults apply (1920x1080@25). Only specific viewport configurations are + supported. The server will reject unsupported combinations. Supported + resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, + 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will be + automatically determined from the width and height if they match a supported configuration exactly. Note: Higher resolutions may affect the responsiveness of live view browser @@ -209,7 +209,7 @@ def update( size: Number of browsers to create in the pool discard_all_idle: Whether to discard all idle browsers and rebuild the pool immediately. Defaults - to true. + to false. extensions: List of browser extensions to load into the session. Provide each by id or name. @@ -236,11 +236,11 @@ def update( are destroyed. Defaults to 600 seconds if not specified viewport: Initial browser window size in pixels with optional refresh rate. If omitted, - image defaults apply (commonly 1024x768@60). Only specific viewport - configurations are supported. The server will reject unsupported combinations. - Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, - 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will - be automatically determined from the width and height if they match a supported + image defaults apply (1920x1080@25). Only specific viewport configurations are + supported. The server will reject unsupported combinations. Supported + resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, + 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will be + automatically determined from the width and height if they match a supported configuration exactly. Note: Higher resolutions may affect the responsiveness of live view browser @@ -540,11 +540,11 @@ async def create( are destroyed. Defaults to 600 seconds if not specified viewport: Initial browser window size in pixels with optional refresh rate. If omitted, - image defaults apply (commonly 1024x768@60). Only specific viewport - configurations are supported. The server will reject unsupported combinations. - Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, - 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will - be automatically determined from the width and height if they match a supported + image defaults apply (1920x1080@25). Only specific viewport configurations are + supported. The server will reject unsupported combinations. Supported + resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, + 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will be + automatically determined from the width and height if they match a supported configuration exactly. Note: Higher resolutions may affect the responsiveness of live view browser @@ -643,7 +643,7 @@ async def update( size: Number of browsers to create in the pool discard_all_idle: Whether to discard all idle browsers and rebuild the pool immediately. Defaults - to true. + to false. extensions: List of browser extensions to load into the session. Provide each by id or name. @@ -670,11 +670,11 @@ async def update( are destroyed. Defaults to 600 seconds if not specified viewport: Initial browser window size in pixels with optional refresh rate. If omitted, - image defaults apply (commonly 1024x768@60). Only specific viewport - configurations are supported. The server will reject unsupported combinations. - Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, - 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will - be automatically determined from the width and height if they match a supported + image defaults apply (1920x1080@25). Only specific viewport configurations are + supported. The server will reject unsupported combinations. Supported + resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, + 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will be + automatically determined from the width and height if they match a supported configuration exactly. Note: Higher resolutions may affect the responsiveness of live view browser diff --git a/src/kernel/resources/browsers/browsers.py b/src/kernel/resources/browsers/browsers.py index 30888da..cbd1773 100644 --- a/src/kernel/resources/browsers/browsers.py +++ b/src/kernel/resources/browsers/browsers.py @@ -181,11 +181,11 @@ def create( see is +/- 5 seconds around the specified value. viewport: Initial browser window size in pixels with optional refresh rate. If omitted, - image defaults apply (commonly 1024x768@60). Only specific viewport - configurations are supported. The server will reject unsupported combinations. - Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, - 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will - be automatically determined from the width and height if they match a supported + image defaults apply (1920x1080@25). Only specific viewport configurations are + supported. The server will reject unsupported combinations. Supported + resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, + 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will be + automatically determined from the width and height if they match a supported configuration exactly. Note: Higher resolutions may affect the responsiveness of live view browser @@ -526,11 +526,11 @@ async def create( see is +/- 5 seconds around the specified value. viewport: Initial browser window size in pixels with optional refresh rate. If omitted, - image defaults apply (commonly 1024x768@60). Only specific viewport - configurations are supported. The server will reject unsupported combinations. - Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, - 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will - be automatically determined from the width and height if they match a supported + image defaults apply (1920x1080@25). Only specific viewport configurations are + supported. The server will reject unsupported combinations. Supported + resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, + 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will be + automatically determined from the width and height if they match a supported configuration exactly. Note: Higher resolutions may affect the responsiveness of live view browser diff --git a/src/kernel/types/browser_create_params.py b/src/kernel/types/browser_create_params.py index 4a5f479..0818760 100644 --- a/src/kernel/types/browser_create_params.py +++ b/src/kernel/types/browser_create_params.py @@ -69,7 +69,7 @@ class BrowserCreateParams(TypedDict, total=False): viewport: BrowserViewport """Initial browser window size in pixels with optional refresh rate. - If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport + If omitted, image defaults apply (1920x1080@25). Only specific viewport configurations are supported. The server will reject unsupported combinations. Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will diff --git a/src/kernel/types/browser_create_response.py b/src/kernel/types/browser_create_response.py index 344549e..efff854 100644 --- a/src/kernel/types/browser_create_response.py +++ b/src/kernel/types/browser_create_response.py @@ -54,7 +54,7 @@ class BrowserCreateResponse(BaseModel): viewport: Optional[BrowserViewport] = None """Initial browser window size in pixels with optional refresh rate. - If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport + If omitted, image defaults apply (1920x1080@25). Only specific viewport configurations are supported. The server will reject unsupported combinations. Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will diff --git a/src/kernel/types/browser_list_response.py b/src/kernel/types/browser_list_response.py index cfa2ce5..3ce2648 100644 --- a/src/kernel/types/browser_list_response.py +++ b/src/kernel/types/browser_list_response.py @@ -54,7 +54,7 @@ class BrowserListResponse(BaseModel): viewport: Optional[BrowserViewport] = None """Initial browser window size in pixels with optional refresh rate. - If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport + If omitted, image defaults apply (1920x1080@25). Only specific viewport configurations are supported. The server will reject unsupported combinations. Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will diff --git a/src/kernel/types/browser_pool_acquire_response.py b/src/kernel/types/browser_pool_acquire_response.py index 1bb69e8..4b70a87 100644 --- a/src/kernel/types/browser_pool_acquire_response.py +++ b/src/kernel/types/browser_pool_acquire_response.py @@ -54,7 +54,7 @@ class BrowserPoolAcquireResponse(BaseModel): viewport: Optional[BrowserViewport] = None """Initial browser window size in pixels with optional refresh rate. - If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport + If omitted, image defaults apply (1920x1080@25). Only specific viewport configurations are supported. The server will reject unsupported combinations. Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will diff --git a/src/kernel/types/browser_pool_create_params.py b/src/kernel/types/browser_pool_create_params.py index c7f87c6..6c8e815 100644 --- a/src/kernel/types/browser_pool_create_params.py +++ b/src/kernel/types/browser_pool_create_params.py @@ -65,7 +65,7 @@ class BrowserPoolCreateParams(TypedDict, total=False): viewport: BrowserViewport """Initial browser window size in pixels with optional refresh rate. - If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport + If omitted, image defaults apply (1920x1080@25). Only specific viewport configurations are supported. The server will reject unsupported combinations. Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will diff --git a/src/kernel/types/browser_pool_request.py b/src/kernel/types/browser_pool_request.py index c25b3a5..c54fad4 100644 --- a/src/kernel/types/browser_pool_request.py +++ b/src/kernel/types/browser_pool_request.py @@ -63,7 +63,7 @@ class BrowserPoolRequest(BaseModel): viewport: Optional[BrowserViewport] = None """Initial browser window size in pixels with optional refresh rate. - If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport + If omitted, image defaults apply (1920x1080@25). Only specific viewport configurations are supported. The server will reject unsupported combinations. Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will diff --git a/src/kernel/types/browser_pool_update_params.py b/src/kernel/types/browser_pool_update_params.py index ed9a7e8..2cd3be7 100644 --- a/src/kernel/types/browser_pool_update_params.py +++ b/src/kernel/types/browser_pool_update_params.py @@ -19,7 +19,7 @@ class BrowserPoolUpdateParams(TypedDict, total=False): discard_all_idle: bool """Whether to discard all idle browsers and rebuild the pool immediately. - Defaults to true. + Defaults to false. """ extensions: Iterable[BrowserExtension] @@ -71,7 +71,7 @@ class BrowserPoolUpdateParams(TypedDict, total=False): viewport: BrowserViewport """Initial browser window size in pixels with optional refresh rate. - If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport + If omitted, image defaults apply (1920x1080@25). Only specific viewport configurations are supported. The server will reject unsupported combinations. Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will diff --git a/src/kernel/types/browser_retrieve_response.py b/src/kernel/types/browser_retrieve_response.py index 2b49d65..12f58a5 100644 --- a/src/kernel/types/browser_retrieve_response.py +++ b/src/kernel/types/browser_retrieve_response.py @@ -54,7 +54,7 @@ class BrowserRetrieveResponse(BaseModel): viewport: Optional[BrowserViewport] = None """Initial browser window size in pixels with optional refresh rate. - If omitted, image defaults apply (commonly 1024x768@60). Only specific viewport + If omitted, image defaults apply (1920x1080@25). Only specific viewport configurations are supported. The server will reject unsupported combinations. Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60 If refresh_rate is not provided, it will From d7bd8a22d40243c043e14b899dd2006fc0d51a19 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 6 Dec 2025 02:01:10 +0000 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20Enhance=20agent=20authentication=20?= =?UTF-8?q?with=20optional=20login=20page=20URL=20and=20auth=20ch=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .stats.yml | 8 +- api.md | 37 ++ src/kernel/_client.py | 9 + src/kernel/resources/__init__.py | 14 + src/kernel/resources/agents/__init__.py | 33 ++ src/kernel/resources/agents/agents.py | 102 ++++ src/kernel/resources/agents/auth/__init__.py | 33 ++ src/kernel/resources/agents/auth/auth.py | 332 +++++++++++++ .../resources/agents/auth/invocations.py | 448 ++++++++++++++++++ src/kernel/types/agents/__init__.py | 11 + .../agents/agent_auth_discover_response.py | 28 ++ .../agents/agent_auth_invocation_response.py | 22 + .../types/agents/agent_auth_start_response.py | 24 + .../agents/agent_auth_submit_response.py | 34 ++ src/kernel/types/agents/auth/__init__.py | 8 + .../agents/auth/invocation_discover_params.py | 16 + .../agents/auth/invocation_exchange_params.py | 12 + .../auth/invocation_exchange_response.py | 13 + .../agents/auth/invocation_submit_params.py | 13 + src/kernel/types/agents/auth_agent.py | 21 + src/kernel/types/agents/auth_start_params.py | 33 ++ src/kernel/types/agents/discovered_field.py | 28 ++ tests/api_resources/agents/__init__.py | 1 + tests/api_resources/agents/auth/__init__.py | 1 + .../agents/auth/test_invocations.py | 421 ++++++++++++++++ tests/api_resources/agents/test_auth.py | 206 ++++++++ 26 files changed, 1904 insertions(+), 4 deletions(-) create mode 100644 src/kernel/resources/agents/__init__.py create mode 100644 src/kernel/resources/agents/agents.py create mode 100644 src/kernel/resources/agents/auth/__init__.py create mode 100644 src/kernel/resources/agents/auth/auth.py create mode 100644 src/kernel/resources/agents/auth/invocations.py create mode 100644 src/kernel/types/agents/__init__.py create mode 100644 src/kernel/types/agents/agent_auth_discover_response.py create mode 100644 src/kernel/types/agents/agent_auth_invocation_response.py create mode 100644 src/kernel/types/agents/agent_auth_start_response.py create mode 100644 src/kernel/types/agents/agent_auth_submit_response.py create mode 100644 src/kernel/types/agents/auth/__init__.py create mode 100644 src/kernel/types/agents/auth/invocation_discover_params.py create mode 100644 src/kernel/types/agents/auth/invocation_exchange_params.py create mode 100644 src/kernel/types/agents/auth/invocation_exchange_response.py create mode 100644 src/kernel/types/agents/auth/invocation_submit_params.py create mode 100644 src/kernel/types/agents/auth_agent.py create mode 100644 src/kernel/types/agents/auth_start_params.py create mode 100644 src/kernel/types/agents/discovered_field.py create mode 100644 tests/api_resources/agents/__init__.py create mode 100644 tests/api_resources/agents/auth/__init__.py create mode 100644 tests/api_resources/agents/auth/test_invocations.py create mode 100644 tests/api_resources/agents/test_auth.py diff --git a/.stats.yml b/.stats.yml index 44c807a..0c474bb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 74 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-3a68acd8c46e121c66be5b4c30bb4e962967840ca0f31070905baa39635fbc2d.yml -openapi_spec_hash: 9453963fbb01de3e0afb462b16cdf115 -config_hash: 6dbe88d2ba9df1ec46cedbfdb7d00000 +configured_endpoints: 80 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-8a37652fa586b8932466d16285359a89988505f850787f8257d0c4c7053da173.yml +openapi_spec_hash: 042765a113f6d08109e8146b302323ec +config_hash: 113f1e5bc3567628a5d51c70bc00969d diff --git a/api.md b/api.md index fe6b45c..d6384dc 100644 --- a/api.md +++ b/api.md @@ -280,3 +280,40 @@ Methods: - client.browser_pools.acquire(id_or_name, \*\*params) -> BrowserPoolAcquireResponse - client.browser_pools.flush(id_or_name) -> None - client.browser_pools.release(id_or_name, \*\*params) -> None + +# Agents + +## Auth + +Types: + +```python +from kernel.types.agents import ( + AgentAuthDiscoverResponse, + AgentAuthInvocationResponse, + AgentAuthStartResponse, + AgentAuthSubmitResponse, + AuthAgent, + DiscoveredField, +) +``` + +Methods: + +- client.agents.auth.retrieve(id) -> AuthAgent +- client.agents.auth.start(\*\*params) -> AgentAuthStartResponse + +### Invocations + +Types: + +```python +from kernel.types.agents.auth import InvocationExchangeResponse +``` + +Methods: + +- client.agents.auth.invocations.retrieve(invocation_id) -> AgentAuthInvocationResponse +- client.agents.auth.invocations.discover(invocation_id, \*\*params) -> AgentAuthDiscoverResponse +- client.agents.auth.invocations.exchange(invocation_id, \*\*params) -> InvocationExchangeResponse +- client.agents.auth.invocations.submit(invocation_id, \*\*params) -> AgentAuthSubmitResponse diff --git a/src/kernel/_client.py b/src/kernel/_client.py index 37ba489..c941be7 100644 --- a/src/kernel/_client.py +++ b/src/kernel/_client.py @@ -29,6 +29,7 @@ SyncAPIClient, AsyncAPIClient, ) +from .resources.agents import agents from .resources.browsers import browsers __all__ = [ @@ -58,6 +59,7 @@ class Kernel(SyncAPIClient): proxies: proxies.ProxiesResource extensions: extensions.ExtensionsResource browser_pools: browser_pools.BrowserPoolsResource + agents: agents.AgentsResource with_raw_response: KernelWithRawResponse with_streaming_response: KernelWithStreamedResponse @@ -147,6 +149,7 @@ def __init__( self.proxies = proxies.ProxiesResource(self) self.extensions = extensions.ExtensionsResource(self) self.browser_pools = browser_pools.BrowserPoolsResource(self) + self.agents = agents.AgentsResource(self) self.with_raw_response = KernelWithRawResponse(self) self.with_streaming_response = KernelWithStreamedResponse(self) @@ -266,6 +269,7 @@ class AsyncKernel(AsyncAPIClient): proxies: proxies.AsyncProxiesResource extensions: extensions.AsyncExtensionsResource browser_pools: browser_pools.AsyncBrowserPoolsResource + agents: agents.AsyncAgentsResource with_raw_response: AsyncKernelWithRawResponse with_streaming_response: AsyncKernelWithStreamedResponse @@ -355,6 +359,7 @@ def __init__( self.proxies = proxies.AsyncProxiesResource(self) self.extensions = extensions.AsyncExtensionsResource(self) self.browser_pools = browser_pools.AsyncBrowserPoolsResource(self) + self.agents = agents.AsyncAgentsResource(self) self.with_raw_response = AsyncKernelWithRawResponse(self) self.with_streaming_response = AsyncKernelWithStreamedResponse(self) @@ -475,6 +480,7 @@ def __init__(self, client: Kernel) -> None: self.proxies = proxies.ProxiesResourceWithRawResponse(client.proxies) self.extensions = extensions.ExtensionsResourceWithRawResponse(client.extensions) self.browser_pools = browser_pools.BrowserPoolsResourceWithRawResponse(client.browser_pools) + self.agents = agents.AgentsResourceWithRawResponse(client.agents) class AsyncKernelWithRawResponse: @@ -487,6 +493,7 @@ def __init__(self, client: AsyncKernel) -> None: self.proxies = proxies.AsyncProxiesResourceWithRawResponse(client.proxies) self.extensions = extensions.AsyncExtensionsResourceWithRawResponse(client.extensions) self.browser_pools = browser_pools.AsyncBrowserPoolsResourceWithRawResponse(client.browser_pools) + self.agents = agents.AsyncAgentsResourceWithRawResponse(client.agents) class KernelWithStreamedResponse: @@ -499,6 +506,7 @@ def __init__(self, client: Kernel) -> None: self.proxies = proxies.ProxiesResourceWithStreamingResponse(client.proxies) self.extensions = extensions.ExtensionsResourceWithStreamingResponse(client.extensions) self.browser_pools = browser_pools.BrowserPoolsResourceWithStreamingResponse(client.browser_pools) + self.agents = agents.AgentsResourceWithStreamingResponse(client.agents) class AsyncKernelWithStreamedResponse: @@ -511,6 +519,7 @@ def __init__(self, client: AsyncKernel) -> None: self.proxies = proxies.AsyncProxiesResourceWithStreamingResponse(client.proxies) self.extensions = extensions.AsyncExtensionsResourceWithStreamingResponse(client.extensions) self.browser_pools = browser_pools.AsyncBrowserPoolsResourceWithStreamingResponse(client.browser_pools) + self.agents = agents.AsyncAgentsResourceWithStreamingResponse(client.agents) Client = Kernel diff --git a/src/kernel/resources/__init__.py b/src/kernel/resources/__init__.py index cf08046..5de2a85 100644 --- a/src/kernel/resources/__init__.py +++ b/src/kernel/resources/__init__.py @@ -8,6 +8,14 @@ AppsResourceWithStreamingResponse, AsyncAppsResourceWithStreamingResponse, ) +from .agents import ( + AgentsResource, + AsyncAgentsResource, + AgentsResourceWithRawResponse, + AsyncAgentsResourceWithRawResponse, + AgentsResourceWithStreamingResponse, + AsyncAgentsResourceWithStreamingResponse, +) from .proxies import ( ProxiesResource, AsyncProxiesResource, @@ -114,4 +122,10 @@ "AsyncBrowserPoolsResourceWithRawResponse", "BrowserPoolsResourceWithStreamingResponse", "AsyncBrowserPoolsResourceWithStreamingResponse", + "AgentsResource", + "AsyncAgentsResource", + "AgentsResourceWithRawResponse", + "AsyncAgentsResourceWithRawResponse", + "AgentsResourceWithStreamingResponse", + "AsyncAgentsResourceWithStreamingResponse", ] diff --git a/src/kernel/resources/agents/__init__.py b/src/kernel/resources/agents/__init__.py new file mode 100644 index 0000000..cb159eb --- /dev/null +++ b/src/kernel/resources/agents/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .auth import ( + AuthResource, + AsyncAuthResource, + AuthResourceWithRawResponse, + AsyncAuthResourceWithRawResponse, + AuthResourceWithStreamingResponse, + AsyncAuthResourceWithStreamingResponse, +) +from .agents import ( + AgentsResource, + AsyncAgentsResource, + AgentsResourceWithRawResponse, + AsyncAgentsResourceWithRawResponse, + AgentsResourceWithStreamingResponse, + AsyncAgentsResourceWithStreamingResponse, +) + +__all__ = [ + "AuthResource", + "AsyncAuthResource", + "AuthResourceWithRawResponse", + "AsyncAuthResourceWithRawResponse", + "AuthResourceWithStreamingResponse", + "AsyncAuthResourceWithStreamingResponse", + "AgentsResource", + "AsyncAgentsResource", + "AgentsResourceWithRawResponse", + "AsyncAgentsResourceWithRawResponse", + "AgentsResourceWithStreamingResponse", + "AsyncAgentsResourceWithStreamingResponse", +] diff --git a/src/kernel/resources/agents/agents.py b/src/kernel/resources/agents/agents.py new file mode 100644 index 0000000..b7bb580 --- /dev/null +++ b/src/kernel/resources/agents/agents.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from ..._compat import cached_property +from .auth.auth import ( + AuthResource, + AsyncAuthResource, + AuthResourceWithRawResponse, + AsyncAuthResourceWithRawResponse, + AuthResourceWithStreamingResponse, + AsyncAuthResourceWithStreamingResponse, +) +from ..._resource import SyncAPIResource, AsyncAPIResource + +__all__ = ["AgentsResource", "AsyncAgentsResource"] + + +class AgentsResource(SyncAPIResource): + @cached_property + def auth(self) -> AuthResource: + return AuthResource(self._client) + + @cached_property + def with_raw_response(self) -> AgentsResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AgentsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AgentsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return AgentsResourceWithStreamingResponse(self) + + +class AsyncAgentsResource(AsyncAPIResource): + @cached_property + def auth(self) -> AsyncAuthResource: + return AsyncAuthResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncAgentsResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncAgentsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAgentsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return AsyncAgentsResourceWithStreamingResponse(self) + + +class AgentsResourceWithRawResponse: + def __init__(self, agents: AgentsResource) -> None: + self._agents = agents + + @cached_property + def auth(self) -> AuthResourceWithRawResponse: + return AuthResourceWithRawResponse(self._agents.auth) + + +class AsyncAgentsResourceWithRawResponse: + def __init__(self, agents: AsyncAgentsResource) -> None: + self._agents = agents + + @cached_property + def auth(self) -> AsyncAuthResourceWithRawResponse: + return AsyncAuthResourceWithRawResponse(self._agents.auth) + + +class AgentsResourceWithStreamingResponse: + def __init__(self, agents: AgentsResource) -> None: + self._agents = agents + + @cached_property + def auth(self) -> AuthResourceWithStreamingResponse: + return AuthResourceWithStreamingResponse(self._agents.auth) + + +class AsyncAgentsResourceWithStreamingResponse: + def __init__(self, agents: AsyncAgentsResource) -> None: + self._agents = agents + + @cached_property + def auth(self) -> AsyncAuthResourceWithStreamingResponse: + return AsyncAuthResourceWithStreamingResponse(self._agents.auth) diff --git a/src/kernel/resources/agents/auth/__init__.py b/src/kernel/resources/agents/auth/__init__.py new file mode 100644 index 0000000..6130549 --- /dev/null +++ b/src/kernel/resources/agents/auth/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .auth import ( + AuthResource, + AsyncAuthResource, + AuthResourceWithRawResponse, + AsyncAuthResourceWithRawResponse, + AuthResourceWithStreamingResponse, + AsyncAuthResourceWithStreamingResponse, +) +from .invocations import ( + InvocationsResource, + AsyncInvocationsResource, + InvocationsResourceWithRawResponse, + AsyncInvocationsResourceWithRawResponse, + InvocationsResourceWithStreamingResponse, + AsyncInvocationsResourceWithStreamingResponse, +) + +__all__ = [ + "InvocationsResource", + "AsyncInvocationsResource", + "InvocationsResourceWithRawResponse", + "AsyncInvocationsResourceWithRawResponse", + "InvocationsResourceWithStreamingResponse", + "AsyncInvocationsResourceWithStreamingResponse", + "AuthResource", + "AsyncAuthResource", + "AuthResourceWithRawResponse", + "AsyncAuthResourceWithRawResponse", + "AuthResourceWithStreamingResponse", + "AsyncAuthResourceWithStreamingResponse", +] diff --git a/src/kernel/resources/agents/auth/auth.py b/src/kernel/resources/agents/auth/auth.py new file mode 100644 index 0000000..daa8221 --- /dev/null +++ b/src/kernel/resources/agents/auth/auth.py @@ -0,0 +1,332 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import maybe_transform, async_maybe_transform +from ...._compat import cached_property +from .invocations import ( + InvocationsResource, + AsyncInvocationsResource, + InvocationsResourceWithRawResponse, + AsyncInvocationsResourceWithRawResponse, + InvocationsResourceWithStreamingResponse, + AsyncInvocationsResourceWithStreamingResponse, +) +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 ...._base_client import make_request_options +from ....types.agents import auth_start_params +from ....types.agents.auth_agent import AuthAgent +from ....types.agents.agent_auth_start_response import AgentAuthStartResponse + +__all__ = ["AuthResource", "AsyncAuthResource"] + + +class AuthResource(SyncAPIResource): + @cached_property + def invocations(self) -> InvocationsResource: + return InvocationsResource(self._client) + + @cached_property + def with_raw_response(self) -> AuthResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AuthResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AuthResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return AuthResourceWithStreamingResponse(self) + + def retrieve( + self, + id: 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, + ) -> AuthAgent: + """Retrieve an auth agent by its ID. + + Returns the current authentication status of + the managed profile. + + 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: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + f"/agents/auth/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthAgent, + ) + + def start( + self, + *, + profile_name: str, + target_domain: str, + app_logo_url: str | Omit = omit, + login_url: str | Omit = omit, + proxy: auth_start_params.Proxy | 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, + ) -> AgentAuthStartResponse: + """Creates a browser session and returns a handoff code for the hosted flow. + + Uses + standard API key or JWT authentication (not the JWT returned by the exchange + endpoint). + + Args: + profile_name: Name of the profile to use for this flow + + target_domain: Target domain for authentication + + app_logo_url: Optional logo URL for the application + + login_url: Optional login page URL. If provided, will be stored on the agent and used to + skip Phase 1 discovery in future invocations. + + proxy: Optional proxy configuration + + 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( + "/agents/auth/start", + body=maybe_transform( + { + "profile_name": profile_name, + "target_domain": target_domain, + "app_logo_url": app_logo_url, + "login_url": login_url, + "proxy": proxy, + }, + auth_start_params.AuthStartParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentAuthStartResponse, + ) + + +class AsyncAuthResource(AsyncAPIResource): + @cached_property + def invocations(self) -> AsyncInvocationsResource: + return AsyncInvocationsResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncAuthResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncAuthResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAuthResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return AsyncAuthResourceWithStreamingResponse(self) + + async def retrieve( + self, + id: 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, + ) -> AuthAgent: + """Retrieve an auth agent by its ID. + + Returns the current authentication status of + the managed profile. + + 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: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + f"/agents/auth/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthAgent, + ) + + async def start( + self, + *, + profile_name: str, + target_domain: str, + app_logo_url: str | Omit = omit, + login_url: str | Omit = omit, + proxy: auth_start_params.Proxy | 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, + ) -> AgentAuthStartResponse: + """Creates a browser session and returns a handoff code for the hosted flow. + + Uses + standard API key or JWT authentication (not the JWT returned by the exchange + endpoint). + + Args: + profile_name: Name of the profile to use for this flow + + target_domain: Target domain for authentication + + app_logo_url: Optional logo URL for the application + + login_url: Optional login page URL. If provided, will be stored on the agent and used to + skip Phase 1 discovery in future invocations. + + proxy: Optional proxy configuration + + 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( + "/agents/auth/start", + body=await async_maybe_transform( + { + "profile_name": profile_name, + "target_domain": target_domain, + "app_logo_url": app_logo_url, + "login_url": login_url, + "proxy": proxy, + }, + auth_start_params.AuthStartParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentAuthStartResponse, + ) + + +class AuthResourceWithRawResponse: + def __init__(self, auth: AuthResource) -> None: + self._auth = auth + + self.retrieve = to_raw_response_wrapper( + auth.retrieve, + ) + self.start = to_raw_response_wrapper( + auth.start, + ) + + @cached_property + def invocations(self) -> InvocationsResourceWithRawResponse: + return InvocationsResourceWithRawResponse(self._auth.invocations) + + +class AsyncAuthResourceWithRawResponse: + def __init__(self, auth: AsyncAuthResource) -> None: + self._auth = auth + + self.retrieve = async_to_raw_response_wrapper( + auth.retrieve, + ) + self.start = async_to_raw_response_wrapper( + auth.start, + ) + + @cached_property + def invocations(self) -> AsyncInvocationsResourceWithRawResponse: + return AsyncInvocationsResourceWithRawResponse(self._auth.invocations) + + +class AuthResourceWithStreamingResponse: + def __init__(self, auth: AuthResource) -> None: + self._auth = auth + + self.retrieve = to_streamed_response_wrapper( + auth.retrieve, + ) + self.start = to_streamed_response_wrapper( + auth.start, + ) + + @cached_property + def invocations(self) -> InvocationsResourceWithStreamingResponse: + return InvocationsResourceWithStreamingResponse(self._auth.invocations) + + +class AsyncAuthResourceWithStreamingResponse: + def __init__(self, auth: AsyncAuthResource) -> None: + self._auth = auth + + self.retrieve = async_to_streamed_response_wrapper( + auth.retrieve, + ) + self.start = async_to_streamed_response_wrapper( + auth.start, + ) + + @cached_property + def invocations(self) -> AsyncInvocationsResourceWithStreamingResponse: + return AsyncInvocationsResourceWithStreamingResponse(self._auth.invocations) diff --git a/src/kernel/resources/agents/auth/invocations.py b/src/kernel/resources/agents/auth/invocations.py new file mode 100644 index 0000000..15729ed --- /dev/null +++ b/src/kernel/resources/agents/auth/invocations.py @@ -0,0 +1,448 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict + +import httpx + +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import 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 ...._base_client import make_request_options +from ....types.agents.auth import invocation_submit_params, invocation_discover_params, invocation_exchange_params +from ....types.agents.agent_auth_submit_response import AgentAuthSubmitResponse +from ....types.agents.agent_auth_discover_response import AgentAuthDiscoverResponse +from ....types.agents.agent_auth_invocation_response import AgentAuthInvocationResponse +from ....types.agents.auth.invocation_exchange_response import InvocationExchangeResponse + +__all__ = ["InvocationsResource", "AsyncInvocationsResource"] + + +class InvocationsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> InvocationsResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return InvocationsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> InvocationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return InvocationsResourceWithStreamingResponse(self) + + def retrieve( + self, + invocation_id: 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, + ) -> AgentAuthInvocationResponse: + """Returns invocation details including app_name and target_domain. + + Uses the JWT + returned by the exchange endpoint, or standard API key or JWT authentication. + + 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 invocation_id: + raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") + return self._get( + f"/agents/auth/invocations/{invocation_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentAuthInvocationResponse, + ) + + def discover( + self, + invocation_id: str, + *, + login_url: 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, + ) -> AgentAuthDiscoverResponse: + """ + Inspects the target site to detect logged-in state or discover required fields. + Returns 200 with success: true when fields are found, or 4xx/5xx for failures. + Requires the JWT returned by the exchange endpoint. + + Args: + login_url: Optional login page URL. If provided, will override the stored login URL for + this discovery invocation and skip Phase 1 discovery. + + 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 invocation_id: + raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") + return self._post( + f"/agents/auth/invocations/{invocation_id}/discover", + body=maybe_transform({"login_url": login_url}, invocation_discover_params.InvocationDiscoverParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentAuthDiscoverResponse, + ) + + def exchange( + self, + invocation_id: str, + *, + code: 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, + ) -> InvocationExchangeResponse: + """Validates the handoff code and returns a JWT token for subsequent requests. + + No + authentication required (the handoff code serves as the credential). + + Args: + code: Handoff code from start endpoint + + 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 invocation_id: + raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") + return self._post( + f"/agents/auth/invocations/{invocation_id}/exchange", + body=maybe_transform({"code": code}, invocation_exchange_params.InvocationExchangeParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=InvocationExchangeResponse, + ) + + def submit( + self, + invocation_id: str, + *, + field_values: Dict[str, 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, + ) -> AgentAuthSubmitResponse: + """ + Submits field values for the discovered login form and may return additional + auth fields or success. Requires the JWT returned by the exchange endpoint. + + Args: + field_values: Values for the discovered login fields + + 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 invocation_id: + raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") + return self._post( + f"/agents/auth/invocations/{invocation_id}/submit", + body=maybe_transform({"field_values": field_values}, invocation_submit_params.InvocationSubmitParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentAuthSubmitResponse, + ) + + +class AsyncInvocationsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncInvocationsResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncInvocationsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncInvocationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return AsyncInvocationsResourceWithStreamingResponse(self) + + async def retrieve( + self, + invocation_id: 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, + ) -> AgentAuthInvocationResponse: + """Returns invocation details including app_name and target_domain. + + Uses the JWT + returned by the exchange endpoint, or standard API key or JWT authentication. + + 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 invocation_id: + raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") + return await self._get( + f"/agents/auth/invocations/{invocation_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentAuthInvocationResponse, + ) + + async def discover( + self, + invocation_id: str, + *, + login_url: 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, + ) -> AgentAuthDiscoverResponse: + """ + Inspects the target site to detect logged-in state or discover required fields. + Returns 200 with success: true when fields are found, or 4xx/5xx for failures. + Requires the JWT returned by the exchange endpoint. + + Args: + login_url: Optional login page URL. If provided, will override the stored login URL for + this discovery invocation and skip Phase 1 discovery. + + 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 invocation_id: + raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") + return await self._post( + f"/agents/auth/invocations/{invocation_id}/discover", + body=await async_maybe_transform( + {"login_url": login_url}, invocation_discover_params.InvocationDiscoverParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentAuthDiscoverResponse, + ) + + async def exchange( + self, + invocation_id: str, + *, + code: 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, + ) -> InvocationExchangeResponse: + """Validates the handoff code and returns a JWT token for subsequent requests. + + No + authentication required (the handoff code serves as the credential). + + Args: + code: Handoff code from start endpoint + + 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 invocation_id: + raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") + return await self._post( + f"/agents/auth/invocations/{invocation_id}/exchange", + body=await async_maybe_transform({"code": code}, invocation_exchange_params.InvocationExchangeParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=InvocationExchangeResponse, + ) + + async def submit( + self, + invocation_id: str, + *, + field_values: Dict[str, 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, + ) -> AgentAuthSubmitResponse: + """ + Submits field values for the discovered login form and may return additional + auth fields or success. Requires the JWT returned by the exchange endpoint. + + Args: + field_values: Values for the discovered login fields + + 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 invocation_id: + raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") + return await self._post( + f"/agents/auth/invocations/{invocation_id}/submit", + body=await async_maybe_transform( + {"field_values": field_values}, invocation_submit_params.InvocationSubmitParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentAuthSubmitResponse, + ) + + +class InvocationsResourceWithRawResponse: + def __init__(self, invocations: InvocationsResource) -> None: + self._invocations = invocations + + self.retrieve = to_raw_response_wrapper( + invocations.retrieve, + ) + self.discover = to_raw_response_wrapper( + invocations.discover, + ) + self.exchange = to_raw_response_wrapper( + invocations.exchange, + ) + self.submit = to_raw_response_wrapper( + invocations.submit, + ) + + +class AsyncInvocationsResourceWithRawResponse: + def __init__(self, invocations: AsyncInvocationsResource) -> None: + self._invocations = invocations + + self.retrieve = async_to_raw_response_wrapper( + invocations.retrieve, + ) + self.discover = async_to_raw_response_wrapper( + invocations.discover, + ) + self.exchange = async_to_raw_response_wrapper( + invocations.exchange, + ) + self.submit = async_to_raw_response_wrapper( + invocations.submit, + ) + + +class InvocationsResourceWithStreamingResponse: + def __init__(self, invocations: InvocationsResource) -> None: + self._invocations = invocations + + self.retrieve = to_streamed_response_wrapper( + invocations.retrieve, + ) + self.discover = to_streamed_response_wrapper( + invocations.discover, + ) + self.exchange = to_streamed_response_wrapper( + invocations.exchange, + ) + self.submit = to_streamed_response_wrapper( + invocations.submit, + ) + + +class AsyncInvocationsResourceWithStreamingResponse: + def __init__(self, invocations: AsyncInvocationsResource) -> None: + self._invocations = invocations + + self.retrieve = async_to_streamed_response_wrapper( + invocations.retrieve, + ) + self.discover = async_to_streamed_response_wrapper( + invocations.discover, + ) + self.exchange = async_to_streamed_response_wrapper( + invocations.exchange, + ) + self.submit = async_to_streamed_response_wrapper( + invocations.submit, + ) diff --git a/src/kernel/types/agents/__init__.py b/src/kernel/types/agents/__init__.py new file mode 100644 index 0000000..1fdcc09 --- /dev/null +++ b/src/kernel/types/agents/__init__.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .auth_agent import AuthAgent as AuthAgent +from .discovered_field import DiscoveredField as DiscoveredField +from .auth_start_params import AuthStartParams as AuthStartParams +from .agent_auth_start_response import AgentAuthStartResponse as AgentAuthStartResponse +from .agent_auth_submit_response import AgentAuthSubmitResponse as AgentAuthSubmitResponse +from .agent_auth_discover_response import AgentAuthDiscoverResponse as AgentAuthDiscoverResponse +from .agent_auth_invocation_response import AgentAuthInvocationResponse as AgentAuthInvocationResponse diff --git a/src/kernel/types/agents/agent_auth_discover_response.py b/src/kernel/types/agents/agent_auth_discover_response.py new file mode 100644 index 0000000..000bdec --- /dev/null +++ b/src/kernel/types/agents/agent_auth_discover_response.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel +from .discovered_field import DiscoveredField + +__all__ = ["AgentAuthDiscoverResponse"] + + +class AgentAuthDiscoverResponse(BaseModel): + success: bool + """Whether discovery succeeded""" + + error_message: Optional[str] = None + """Error message if discovery failed""" + + fields: Optional[List[DiscoveredField]] = None + """Discovered form fields (present when success is true)""" + + logged_in: Optional[bool] = None + """Whether user is already logged in""" + + login_url: Optional[str] = None + """URL of the discovered login page""" + + page_title: Optional[str] = None + """Title of the login page""" diff --git a/src/kernel/types/agents/agent_auth_invocation_response.py b/src/kernel/types/agents/agent_auth_invocation_response.py new file mode 100644 index 0000000..82b5f80 --- /dev/null +++ b/src/kernel/types/agents/agent_auth_invocation_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["AgentAuthInvocationResponse"] + + +class AgentAuthInvocationResponse(BaseModel): + app_name: str + """App name (org name at time of invocation creation)""" + + expires_at: datetime + """When the handoff code expires""" + + status: Literal["IN_PROGRESS", "SUCCESS", "EXPIRED", "CANCELED"] + """Invocation status""" + + target_domain: str + """Target domain for authentication""" diff --git a/src/kernel/types/agents/agent_auth_start_response.py b/src/kernel/types/agents/agent_auth_start_response.py new file mode 100644 index 0000000..3287ba0 --- /dev/null +++ b/src/kernel/types/agents/agent_auth_start_response.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime + +from ..._models import BaseModel + +__all__ = ["AgentAuthStartResponse"] + + +class AgentAuthStartResponse(BaseModel): + auth_agent_id: str + """Unique identifier for the auth agent managing this domain/profile""" + + expires_at: datetime + """When the handoff code expires""" + + handoff_code: str + """One-time code for handoff""" + + hosted_url: str + """URL to redirect user to""" + + invocation_id: str + """Unique identifier for the invocation""" diff --git a/src/kernel/types/agents/agent_auth_submit_response.py b/src/kernel/types/agents/agent_auth_submit_response.py new file mode 100644 index 0000000..c57002f --- /dev/null +++ b/src/kernel/types/agents/agent_auth_submit_response.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel +from .discovered_field import DiscoveredField + +__all__ = ["AgentAuthSubmitResponse"] + + +class AgentAuthSubmitResponse(BaseModel): + success: bool + """Whether submission succeeded""" + + additional_fields: Optional[List[DiscoveredField]] = None + """ + Additional fields needed (e.g., OTP) - present when needs_additional_auth is + true + """ + + app_name: Optional[str] = None + """App name (only present when logged_in is true)""" + + error_message: Optional[str] = None + """Error message if submission failed""" + + logged_in: Optional[bool] = None + """Whether user is now logged in""" + + needs_additional_auth: Optional[bool] = None + """Whether additional authentication fields are needed""" + + target_domain: Optional[str] = None + """Target domain (only present when logged_in is true)""" diff --git a/src/kernel/types/agents/auth/__init__.py b/src/kernel/types/agents/auth/__init__.py new file mode 100644 index 0000000..bfbd280 --- /dev/null +++ b/src/kernel/types/agents/auth/__init__.py @@ -0,0 +1,8 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .invocation_submit_params import InvocationSubmitParams as InvocationSubmitParams +from .invocation_discover_params import InvocationDiscoverParams as InvocationDiscoverParams +from .invocation_exchange_params import InvocationExchangeParams as InvocationExchangeParams +from .invocation_exchange_response import InvocationExchangeResponse as InvocationExchangeResponse diff --git a/src/kernel/types/agents/auth/invocation_discover_params.py b/src/kernel/types/agents/auth/invocation_discover_params.py new file mode 100644 index 0000000..aa03f0c --- /dev/null +++ b/src/kernel/types/agents/auth/invocation_discover_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["InvocationDiscoverParams"] + + +class InvocationDiscoverParams(TypedDict, total=False): + login_url: str + """Optional login page URL. + + If provided, will override the stored login URL for this discovery invocation + and skip Phase 1 discovery. + """ diff --git a/src/kernel/types/agents/auth/invocation_exchange_params.py b/src/kernel/types/agents/auth/invocation_exchange_params.py new file mode 100644 index 0000000..71e4d18 --- /dev/null +++ b/src/kernel/types/agents/auth/invocation_exchange_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["InvocationExchangeParams"] + + +class InvocationExchangeParams(TypedDict, total=False): + code: Required[str] + """Handoff code from start endpoint""" diff --git a/src/kernel/types/agents/auth/invocation_exchange_response.py b/src/kernel/types/agents/auth/invocation_exchange_response.py new file mode 100644 index 0000000..91b74ce --- /dev/null +++ b/src/kernel/types/agents/auth/invocation_exchange_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ...._models import BaseModel + +__all__ = ["InvocationExchangeResponse"] + + +class InvocationExchangeResponse(BaseModel): + invocation_id: str + """Invocation ID""" + + jwt: str + """JWT token with invocation_id claim (30 minute TTL)""" diff --git a/src/kernel/types/agents/auth/invocation_submit_params.py b/src/kernel/types/agents/auth/invocation_submit_params.py new file mode 100644 index 0000000..be92e7d --- /dev/null +++ b/src/kernel/types/agents/auth/invocation_submit_params.py @@ -0,0 +1,13 @@ +# 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__ = ["InvocationSubmitParams"] + + +class InvocationSubmitParams(TypedDict, total=False): + field_values: Required[Dict[str, str]] + """Values for the discovered login fields""" diff --git a/src/kernel/types/agents/auth_agent.py b/src/kernel/types/agents/auth_agent.py new file mode 100644 index 0000000..8671f97 --- /dev/null +++ b/src/kernel/types/agents/auth_agent.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["AuthAgent"] + + +class AuthAgent(BaseModel): + id: str + """Unique identifier for the auth agent""" + + domain: str + """Target domain for authentication""" + + profile_name: str + """Name of the profile associated with this auth agent""" + + status: Literal["AUTHENTICATED", "NEEDS_AUTH"] + """Current authentication status of the managed profile""" diff --git a/src/kernel/types/agents/auth_start_params.py b/src/kernel/types/agents/auth_start_params.py new file mode 100644 index 0000000..9c9fb35 --- /dev/null +++ b/src/kernel/types/agents/auth_start_params.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["AuthStartParams", "Proxy"] + + +class AuthStartParams(TypedDict, total=False): + profile_name: Required[str] + """Name of the profile to use for this flow""" + + target_domain: Required[str] + """Target domain for authentication""" + + app_logo_url: str + """Optional logo URL for the application""" + + login_url: str + """Optional login page URL. + + If provided, will be stored on the agent and used to skip Phase 1 discovery in + future invocations. + """ + + proxy: Proxy + """Optional proxy configuration""" + + +class Proxy(TypedDict, total=False): + proxy_id: str + """ID of the proxy to use""" diff --git a/src/kernel/types/agents/discovered_field.py b/src/kernel/types/agents/discovered_field.py new file mode 100644 index 0000000..d1b9dc9 --- /dev/null +++ b/src/kernel/types/agents/discovered_field.py @@ -0,0 +1,28 @@ +# 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 + +__all__ = ["DiscoveredField"] + + +class DiscoveredField(BaseModel): + label: str + """Field label""" + + name: str + """Field name""" + + selector: str + """CSS selector for the field""" + + type: Literal["text", "email", "password", "tel", "number", "url", "code"] + """Field type""" + + placeholder: Optional[str] = None + """Field placeholder""" + + required: Optional[bool] = None + """Whether field is required""" diff --git a/tests/api_resources/agents/__init__.py b/tests/api_resources/agents/__init__.py new file mode 100644 index 0000000..fd8019a --- /dev/null +++ b/tests/api_resources/agents/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/agents/auth/__init__.py b/tests/api_resources/agents/auth/__init__.py new file mode 100644 index 0000000..fd8019a --- /dev/null +++ b/tests/api_resources/agents/auth/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/agents/auth/test_invocations.py b/tests/api_resources/agents/auth/test_invocations.py new file mode 100644 index 0000000..e9c3d8f --- /dev/null +++ b/tests/api_resources/agents/auth/test_invocations.py @@ -0,0 +1,421 @@ +# 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.types.agents import AgentAuthSubmitResponse, AgentAuthDiscoverResponse, AgentAuthInvocationResponse +from kernel.types.agents.auth import ( + InvocationExchangeResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestInvocations: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_retrieve(self, client: Kernel) -> None: + invocation = client.agents.auth.invocations.retrieve( + "invocation_id", + ) + assert_matches_type(AgentAuthInvocationResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Kernel) -> None: + response = client.agents.auth.invocations.with_raw_response.retrieve( + "invocation_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = response.parse() + assert_matches_type(AgentAuthInvocationResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Kernel) -> None: + with client.agents.auth.invocations.with_streaming_response.retrieve( + "invocation_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invocation = response.parse() + assert_matches_type(AgentAuthInvocationResponse, invocation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism 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 `invocation_id` but received ''"): + client.agents.auth.invocations.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_discover(self, client: Kernel) -> None: + invocation = client.agents.auth.invocations.discover( + invocation_id="invocation_id", + ) + assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_discover_with_all_params(self, client: Kernel) -> None: + invocation = client.agents.auth.invocations.discover( + invocation_id="invocation_id", + login_url="https://doordash.com/account/login", + ) + assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_discover(self, client: Kernel) -> None: + response = client.agents.auth.invocations.with_raw_response.discover( + invocation_id="invocation_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = response.parse() + assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_discover(self, client: Kernel) -> None: + with client.agents.auth.invocations.with_streaming_response.discover( + invocation_id="invocation_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invocation = response.parse() + assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_discover(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invocation_id` but received ''"): + client.agents.auth.invocations.with_raw_response.discover( + invocation_id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_exchange(self, client: Kernel) -> None: + invocation = client.agents.auth.invocations.exchange( + invocation_id="invocation_id", + code="abc123xyz", + ) + assert_matches_type(InvocationExchangeResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_exchange(self, client: Kernel) -> None: + response = client.agents.auth.invocations.with_raw_response.exchange( + invocation_id="invocation_id", + code="abc123xyz", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = response.parse() + assert_matches_type(InvocationExchangeResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_exchange(self, client: Kernel) -> None: + with client.agents.auth.invocations.with_streaming_response.exchange( + invocation_id="invocation_id", + code="abc123xyz", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invocation = response.parse() + assert_matches_type(InvocationExchangeResponse, invocation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_exchange(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invocation_id` but received ''"): + client.agents.auth.invocations.with_raw_response.exchange( + invocation_id="", + code="abc123xyz", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_submit(self, client: Kernel) -> None: + invocation = client.agents.auth.invocations.submit( + invocation_id="invocation_id", + field_values={ + "email": "user@example.com", + "password": "********", + }, + ) + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_submit(self, client: Kernel) -> None: + response = client.agents.auth.invocations.with_raw_response.submit( + invocation_id="invocation_id", + field_values={ + "email": "user@example.com", + "password": "********", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = response.parse() + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_submit(self, client: Kernel) -> None: + with client.agents.auth.invocations.with_streaming_response.submit( + invocation_id="invocation_id", + field_values={ + "email": "user@example.com", + "password": "********", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invocation = response.parse() + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_submit(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invocation_id` but received ''"): + client.agents.auth.invocations.with_raw_response.submit( + invocation_id="", + field_values={ + "email": "user@example.com", + "password": "********", + }, + ) + + +class TestAsyncInvocations: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncKernel) -> None: + invocation = await async_client.agents.auth.invocations.retrieve( + "invocation_id", + ) + assert_matches_type(AgentAuthInvocationResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: + response = await async_client.agents.auth.invocations.with_raw_response.retrieve( + "invocation_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = await response.parse() + assert_matches_type(AgentAuthInvocationResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> None: + async with async_client.agents.auth.invocations.with_streaming_response.retrieve( + "invocation_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invocation = await response.parse() + assert_matches_type(AgentAuthInvocationResponse, invocation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism 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 `invocation_id` but received ''"): + await async_client.agents.auth.invocations.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_discover(self, async_client: AsyncKernel) -> None: + invocation = await async_client.agents.auth.invocations.discover( + invocation_id="invocation_id", + ) + assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_discover_with_all_params(self, async_client: AsyncKernel) -> None: + invocation = await async_client.agents.auth.invocations.discover( + invocation_id="invocation_id", + login_url="https://doordash.com/account/login", + ) + assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_discover(self, async_client: AsyncKernel) -> None: + response = await async_client.agents.auth.invocations.with_raw_response.discover( + invocation_id="invocation_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = await response.parse() + assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_discover(self, async_client: AsyncKernel) -> None: + async with async_client.agents.auth.invocations.with_streaming_response.discover( + invocation_id="invocation_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invocation = await response.parse() + assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_discover(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invocation_id` but received ''"): + await async_client.agents.auth.invocations.with_raw_response.discover( + invocation_id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_exchange(self, async_client: AsyncKernel) -> None: + invocation = await async_client.agents.auth.invocations.exchange( + invocation_id="invocation_id", + code="abc123xyz", + ) + assert_matches_type(InvocationExchangeResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_exchange(self, async_client: AsyncKernel) -> None: + response = await async_client.agents.auth.invocations.with_raw_response.exchange( + invocation_id="invocation_id", + code="abc123xyz", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = await response.parse() + assert_matches_type(InvocationExchangeResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_exchange(self, async_client: AsyncKernel) -> None: + async with async_client.agents.auth.invocations.with_streaming_response.exchange( + invocation_id="invocation_id", + code="abc123xyz", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invocation = await response.parse() + assert_matches_type(InvocationExchangeResponse, invocation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_exchange(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invocation_id` but received ''"): + await async_client.agents.auth.invocations.with_raw_response.exchange( + invocation_id="", + code="abc123xyz", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_submit(self, async_client: AsyncKernel) -> None: + invocation = await async_client.agents.auth.invocations.submit( + invocation_id="invocation_id", + field_values={ + "email": "user@example.com", + "password": "********", + }, + ) + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_submit(self, async_client: AsyncKernel) -> None: + response = await async_client.agents.auth.invocations.with_raw_response.submit( + invocation_id="invocation_id", + field_values={ + "email": "user@example.com", + "password": "********", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = await response.parse() + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_submit(self, async_client: AsyncKernel) -> None: + async with async_client.agents.auth.invocations.with_streaming_response.submit( + invocation_id="invocation_id", + field_values={ + "email": "user@example.com", + "password": "********", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invocation = await response.parse() + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_submit(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `invocation_id` but received ''"): + await async_client.agents.auth.invocations.with_raw_response.submit( + invocation_id="", + field_values={ + "email": "user@example.com", + "password": "********", + }, + ) diff --git a/tests/api_resources/agents/test_auth.py b/tests/api_resources/agents/test_auth.py new file mode 100644 index 0000000..0dc190d --- /dev/null +++ b/tests/api_resources/agents/test_auth.py @@ -0,0 +1,206 @@ +# 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.types.agents import AuthAgent, AgentAuthStartResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAuth: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_retrieve(self, client: Kernel) -> None: + auth = client.agents.auth.retrieve( + "id", + ) + assert_matches_type(AuthAgent, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Kernel) -> None: + response = client.agents.auth.with_raw_response.retrieve( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + auth = response.parse() + assert_matches_type(AuthAgent, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Kernel) -> None: + with client.agents.auth.with_streaming_response.retrieve( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + auth = response.parse() + assert_matches_type(AuthAgent, auth, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism 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` but received ''"): + client.agents.auth.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_start(self, client: Kernel) -> None: + auth = client.agents.auth.start( + profile_name="auth-abc123", + target_domain="doordash.com", + ) + assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_start_with_all_params(self, client: Kernel) -> None: + auth = client.agents.auth.start( + profile_name="auth-abc123", + target_domain="doordash.com", + app_logo_url="https://example.com/logo.png", + login_url="https://doordash.com/account/login", + proxy={"proxy_id": "proxy_id"}, + ) + assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_start(self, client: Kernel) -> None: + response = client.agents.auth.with_raw_response.start( + profile_name="auth-abc123", + target_domain="doordash.com", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + auth = response.parse() + assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_start(self, client: Kernel) -> None: + with client.agents.auth.with_streaming_response.start( + profile_name="auth-abc123", + target_domain="doordash.com", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + auth = response.parse() + assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncAuth: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncKernel) -> None: + auth = await async_client.agents.auth.retrieve( + "id", + ) + assert_matches_type(AuthAgent, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: + response = await async_client.agents.auth.with_raw_response.retrieve( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + auth = await response.parse() + assert_matches_type(AuthAgent, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> None: + async with async_client.agents.auth.with_streaming_response.retrieve( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + auth = await response.parse() + assert_matches_type(AuthAgent, auth, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism 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` but received ''"): + await async_client.agents.auth.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_start(self, async_client: AsyncKernel) -> None: + auth = await async_client.agents.auth.start( + profile_name="auth-abc123", + target_domain="doordash.com", + ) + assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_start_with_all_params(self, async_client: AsyncKernel) -> None: + auth = await async_client.agents.auth.start( + profile_name="auth-abc123", + target_domain="doordash.com", + app_logo_url="https://example.com/logo.png", + login_url="https://doordash.com/account/login", + proxy={"proxy_id": "proxy_id"}, + ) + assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_start(self, async_client: AsyncKernel) -> None: + response = await async_client.agents.auth.with_raw_response.start( + profile_name="auth-abc123", + target_domain="doordash.com", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + auth = await response.parse() + assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_start(self, async_client: AsyncKernel) -> None: + async with async_client.agents.auth.with_streaming_response.start( + profile_name="auth-abc123", + target_domain="doordash.com", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + auth = await response.parse() + assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + + assert cast(Any, response.is_closed) is True From 84a794c35f5a454b047d53b37867586f85e2536a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 06:55:42 +0000 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20enhance=20agent=20authentication=20?= =?UTF-8?q?API=20with=20new=20endpoints=20and=20request=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .stats.yml | 8 +- api.md | 8 +- src/kernel/resources/agents/auth/auth.py | 268 +++++++++++++----- .../resources/agents/auth/invocations.py | 96 ++++++- src/kernel/types/agents/__init__.py | 7 +- src/kernel/types/agents/auth/__init__.py | 1 + .../agents/auth/invocation_create_params.py | 12 + ... auth_agent_invocation_create_response.py} | 7 +- ..._start_params.py => auth_create_params.py} | 13 +- src/kernel/types/agents/auth_list_params.py | 21 ++ .../agents/auth/test_invocations.py | 75 ++++- tests/api_resources/agents/test_auth.py | 183 ++++++++---- 12 files changed, 546 insertions(+), 153 deletions(-) create mode 100644 src/kernel/types/agents/auth/invocation_create_params.py rename src/kernel/types/agents/{agent_auth_start_response.py => auth_agent_invocation_create_response.py} (69%) rename src/kernel/types/agents/{auth_start_params.py => auth_create_params.py} (61%) create mode 100644 src/kernel/types/agents/auth_list_params.py diff --git a/.stats.yml b/.stats.yml index 0c474bb..4bf353a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 80 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-8a37652fa586b8932466d16285359a89988505f850787f8257d0c4c7053da173.yml -openapi_spec_hash: 042765a113f6d08109e8146b302323ec -config_hash: 113f1e5bc3567628a5d51c70bc00969d +configured_endpoints: 82 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-b6957db438b01d979b62de21d4e674601b37d55b850b95a6e2b4c771aad5e840.yml +openapi_spec_hash: 1c8aac8322bc9df8f1b82a7e7a0c692b +config_hash: a4b4d14bdf6af723b235a6981977627c diff --git a/api.md b/api.md index d6384dc..2876b33 100644 --- a/api.md +++ b/api.md @@ -291,17 +291,20 @@ Types: from kernel.types.agents import ( AgentAuthDiscoverResponse, AgentAuthInvocationResponse, - AgentAuthStartResponse, AgentAuthSubmitResponse, AuthAgent, + AuthAgentCreateRequest, + AuthAgentInvocationCreateRequest, + AuthAgentInvocationCreateResponse, DiscoveredField, ) ``` Methods: +- client.agents.auth.create(\*\*params) -> AuthAgent - client.agents.auth.retrieve(id) -> AuthAgent -- client.agents.auth.start(\*\*params) -> AgentAuthStartResponse +- client.agents.auth.list(\*\*params) -> SyncOffsetPagination[AuthAgent] ### Invocations @@ -313,6 +316,7 @@ from kernel.types.agents.auth import InvocationExchangeResponse Methods: +- client.agents.auth.invocations.create(\*\*params) -> AuthAgentInvocationCreateResponse - client.agents.auth.invocations.retrieve(invocation_id) -> AgentAuthInvocationResponse - client.agents.auth.invocations.discover(invocation_id, \*\*params) -> AgentAuthDiscoverResponse - client.agents.auth.invocations.exchange(invocation_id, \*\*params) -> InvocationExchangeResponse diff --git a/src/kernel/resources/agents/auth/auth.py b/src/kernel/resources/agents/auth/auth.py index daa8221..b4fc758 100644 --- a/src/kernel/resources/agents/auth/auth.py +++ b/src/kernel/resources/agents/auth/auth.py @@ -22,10 +22,10 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ...._base_client import make_request_options -from ....types.agents import auth_start_params +from ....pagination import SyncOffsetPagination, AsyncOffsetPagination +from ...._base_client import AsyncPaginator, make_request_options +from ....types.agents import auth_list_params, auth_create_params from ....types.agents.auth_agent import AuthAgent -from ....types.agents.agent_auth_start_response import AgentAuthStartResponse __all__ = ["AuthResource", "AsyncAuthResource"] @@ -54,6 +54,61 @@ def with_streaming_response(self) -> AuthResourceWithStreamingResponse: """ return AuthResourceWithStreamingResponse(self) + def create( + self, + *, + profile_name: str, + target_domain: str, + login_url: str | Omit = omit, + proxy: auth_create_params.Proxy | 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, + ) -> AuthAgent: + """ + Creates a new auth agent for the specified domain and profile combination, or + returns an existing one if it already exists. This is idempotent - calling with + the same domain and profile will return the same agent. Does NOT start an + invocation - use POST /agents/auth/invocations to start an auth flow. + + Args: + profile_name: Name of the profile to use for this auth agent + + target_domain: Target domain for authentication + + login_url: Optional login page URL. If provided, will be stored on the agent and used to + skip discovery in future invocations. + + proxy: Optional proxy configuration + + 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( + "/agents/auth", + body=maybe_transform( + { + "profile_name": profile_name, + "target_domain": target_domain, + "login_url": login_url, + "proxy": proxy, + }, + auth_create_params.AuthCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthAgent, + ) + def retrieve( self, id: str, @@ -89,38 +144,31 @@ def retrieve( cast_to=AuthAgent, ) - def start( + def list( self, *, - profile_name: str, - target_domain: str, - app_logo_url: str | Omit = omit, - login_url: str | Omit = omit, - proxy: auth_start_params.Proxy | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + profile_name: str | Omit = omit, + target_domain: 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, - ) -> AgentAuthStartResponse: - """Creates a browser session and returns a handoff code for the hosted flow. - - Uses - standard API key or JWT authentication (not the JWT returned by the exchange - endpoint). + ) -> SyncOffsetPagination[AuthAgent]: + """ + List auth agents with optional filters for profile_name and target_domain. Args: - profile_name: Name of the profile to use for this flow + limit: Maximum number of results to return - target_domain: Target domain for authentication - - app_logo_url: Optional logo URL for the application + offset: Number of results to skip - login_url: Optional login page URL. If provided, will be stored on the agent and used to - skip Phase 1 discovery in future invocations. + profile_name: Filter by profile name - proxy: Optional proxy configuration + target_domain: Filter by target domain extra_headers: Send extra headers @@ -130,22 +178,25 @@ def start( timeout: Override the client-level default timeout for this request, in seconds """ - return self._post( - "/agents/auth/start", - body=maybe_transform( - { - "profile_name": profile_name, - "target_domain": target_domain, - "app_logo_url": app_logo_url, - "login_url": login_url, - "proxy": proxy, - }, - auth_start_params.AuthStartParams, - ), + return self._get_api_list( + "/agents/auth", + page=SyncOffsetPagination[AuthAgent], options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "offset": offset, + "profile_name": profile_name, + "target_domain": target_domain, + }, + auth_list_params.AuthListParams, + ), ), - cast_to=AgentAuthStartResponse, + model=AuthAgent, ) @@ -173,6 +224,61 @@ def with_streaming_response(self) -> AsyncAuthResourceWithStreamingResponse: """ return AsyncAuthResourceWithStreamingResponse(self) + async def create( + self, + *, + profile_name: str, + target_domain: str, + login_url: str | Omit = omit, + proxy: auth_create_params.Proxy | 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, + ) -> AuthAgent: + """ + Creates a new auth agent for the specified domain and profile combination, or + returns an existing one if it already exists. This is idempotent - calling with + the same domain and profile will return the same agent. Does NOT start an + invocation - use POST /agents/auth/invocations to start an auth flow. + + Args: + profile_name: Name of the profile to use for this auth agent + + target_domain: Target domain for authentication + + login_url: Optional login page URL. If provided, will be stored on the agent and used to + skip discovery in future invocations. + + proxy: Optional proxy configuration + + 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( + "/agents/auth", + body=await async_maybe_transform( + { + "profile_name": profile_name, + "target_domain": target_domain, + "login_url": login_url, + "proxy": proxy, + }, + auth_create_params.AuthCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthAgent, + ) + async def retrieve( self, id: str, @@ -208,38 +314,31 @@ async def retrieve( cast_to=AuthAgent, ) - async def start( + def list( self, *, - profile_name: str, - target_domain: str, - app_logo_url: str | Omit = omit, - login_url: str | Omit = omit, - proxy: auth_start_params.Proxy | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + profile_name: str | Omit = omit, + target_domain: 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, - ) -> AgentAuthStartResponse: - """Creates a browser session and returns a handoff code for the hosted flow. - - Uses - standard API key or JWT authentication (not the JWT returned by the exchange - endpoint). + ) -> AsyncPaginator[AuthAgent, AsyncOffsetPagination[AuthAgent]]: + """ + List auth agents with optional filters for profile_name and target_domain. Args: - profile_name: Name of the profile to use for this flow + limit: Maximum number of results to return - target_domain: Target domain for authentication + offset: Number of results to skip - app_logo_url: Optional logo URL for the application + profile_name: Filter by profile name - login_url: Optional login page URL. If provided, will be stored on the agent and used to - skip Phase 1 discovery in future invocations. - - proxy: Optional proxy configuration + target_domain: Filter by target domain extra_headers: Send extra headers @@ -249,22 +348,25 @@ async def start( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._post( - "/agents/auth/start", - body=await async_maybe_transform( - { - "profile_name": profile_name, - "target_domain": target_domain, - "app_logo_url": app_logo_url, - "login_url": login_url, - "proxy": proxy, - }, - auth_start_params.AuthStartParams, - ), + return self._get_api_list( + "/agents/auth", + page=AsyncOffsetPagination[AuthAgent], options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "offset": offset, + "profile_name": profile_name, + "target_domain": target_domain, + }, + auth_list_params.AuthListParams, + ), ), - cast_to=AgentAuthStartResponse, + model=AuthAgent, ) @@ -272,11 +374,14 @@ class AuthResourceWithRawResponse: def __init__(self, auth: AuthResource) -> None: self._auth = auth + self.create = to_raw_response_wrapper( + auth.create, + ) self.retrieve = to_raw_response_wrapper( auth.retrieve, ) - self.start = to_raw_response_wrapper( - auth.start, + self.list = to_raw_response_wrapper( + auth.list, ) @cached_property @@ -288,11 +393,14 @@ class AsyncAuthResourceWithRawResponse: def __init__(self, auth: AsyncAuthResource) -> None: self._auth = auth + self.create = async_to_raw_response_wrapper( + auth.create, + ) self.retrieve = async_to_raw_response_wrapper( auth.retrieve, ) - self.start = async_to_raw_response_wrapper( - auth.start, + self.list = async_to_raw_response_wrapper( + auth.list, ) @cached_property @@ -304,11 +412,14 @@ class AuthResourceWithStreamingResponse: def __init__(self, auth: AuthResource) -> None: self._auth = auth + self.create = to_streamed_response_wrapper( + auth.create, + ) self.retrieve = to_streamed_response_wrapper( auth.retrieve, ) - self.start = to_streamed_response_wrapper( - auth.start, + self.list = to_streamed_response_wrapper( + auth.list, ) @cached_property @@ -320,11 +431,14 @@ class AsyncAuthResourceWithStreamingResponse: def __init__(self, auth: AsyncAuthResource) -> None: self._auth = auth + self.create = async_to_streamed_response_wrapper( + auth.create, + ) self.retrieve = async_to_streamed_response_wrapper( auth.retrieve, ) - self.start = async_to_streamed_response_wrapper( - auth.start, + self.list = async_to_streamed_response_wrapper( + auth.list, ) @cached_property diff --git a/src/kernel/resources/agents/auth/invocations.py b/src/kernel/resources/agents/auth/invocations.py index 15729ed..361f424 100644 --- a/src/kernel/resources/agents/auth/invocations.py +++ b/src/kernel/resources/agents/auth/invocations.py @@ -17,11 +17,17 @@ async_to_streamed_response_wrapper, ) from ...._base_client import make_request_options -from ....types.agents.auth import invocation_submit_params, invocation_discover_params, invocation_exchange_params +from ....types.agents.auth import ( + invocation_create_params, + invocation_submit_params, + invocation_discover_params, + invocation_exchange_params, +) from ....types.agents.agent_auth_submit_response import AgentAuthSubmitResponse from ....types.agents.agent_auth_discover_response import AgentAuthDiscoverResponse from ....types.agents.agent_auth_invocation_response import AgentAuthInvocationResponse from ....types.agents.auth.invocation_exchange_response import InvocationExchangeResponse +from ....types.agents.auth_agent_invocation_create_response import AuthAgentInvocationCreateResponse __all__ = ["InvocationsResource", "AsyncInvocationsResource"] @@ -46,6 +52,43 @@ def with_streaming_response(self) -> InvocationsResourceWithStreamingResponse: """ return InvocationsResourceWithStreamingResponse(self) + def create( + self, + *, + auth_agent_id: 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, + ) -> AuthAgentInvocationCreateResponse: + """Creates a new authentication invocation for the specified auth agent. + + This + starts the auth flow and returns a hosted URL for the user to complete + authentication. + + Args: + auth_agent_id: ID of the auth agent to create an invocation for + + 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( + "/agents/auth/invocations", + body=maybe_transform({"auth_agent_id": auth_agent_id}, invocation_create_params.InvocationCreateParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthAgentInvocationCreateResponse, + ) + def retrieve( self, invocation_id: str, @@ -219,6 +262,45 @@ def with_streaming_response(self) -> AsyncInvocationsResourceWithStreamingRespon """ return AsyncInvocationsResourceWithStreamingResponse(self) + async def create( + self, + *, + auth_agent_id: 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, + ) -> AuthAgentInvocationCreateResponse: + """Creates a new authentication invocation for the specified auth agent. + + This + starts the auth flow and returns a hosted URL for the user to complete + authentication. + + Args: + auth_agent_id: ID of the auth agent to create an invocation for + + 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( + "/agents/auth/invocations", + body=await async_maybe_transform( + {"auth_agent_id": auth_agent_id}, invocation_create_params.InvocationCreateParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthAgentInvocationCreateResponse, + ) + async def retrieve( self, invocation_id: str, @@ -380,6 +462,9 @@ class InvocationsResourceWithRawResponse: def __init__(self, invocations: InvocationsResource) -> None: self._invocations = invocations + self.create = to_raw_response_wrapper( + invocations.create, + ) self.retrieve = to_raw_response_wrapper( invocations.retrieve, ) @@ -398,6 +483,9 @@ class AsyncInvocationsResourceWithRawResponse: def __init__(self, invocations: AsyncInvocationsResource) -> None: self._invocations = invocations + self.create = async_to_raw_response_wrapper( + invocations.create, + ) self.retrieve = async_to_raw_response_wrapper( invocations.retrieve, ) @@ -416,6 +504,9 @@ class InvocationsResourceWithStreamingResponse: def __init__(self, invocations: InvocationsResource) -> None: self._invocations = invocations + self.create = to_streamed_response_wrapper( + invocations.create, + ) self.retrieve = to_streamed_response_wrapper( invocations.retrieve, ) @@ -434,6 +525,9 @@ class AsyncInvocationsResourceWithStreamingResponse: def __init__(self, invocations: AsyncInvocationsResource) -> None: self._invocations = invocations + self.create = async_to_streamed_response_wrapper( + invocations.create, + ) self.retrieve = async_to_streamed_response_wrapper( invocations.retrieve, ) diff --git a/src/kernel/types/agents/__init__.py b/src/kernel/types/agents/__init__.py index 1fdcc09..686e805 100644 --- a/src/kernel/types/agents/__init__.py +++ b/src/kernel/types/agents/__init__.py @@ -3,9 +3,12 @@ from __future__ import annotations from .auth_agent import AuthAgent as AuthAgent +from .auth_list_params import AuthListParams as AuthListParams from .discovered_field import DiscoveredField as DiscoveredField -from .auth_start_params import AuthStartParams as AuthStartParams -from .agent_auth_start_response import AgentAuthStartResponse as AgentAuthStartResponse +from .auth_create_params import AuthCreateParams as AuthCreateParams from .agent_auth_submit_response import AgentAuthSubmitResponse as AgentAuthSubmitResponse from .agent_auth_discover_response import AgentAuthDiscoverResponse as AgentAuthDiscoverResponse from .agent_auth_invocation_response import AgentAuthInvocationResponse as AgentAuthInvocationResponse +from .auth_agent_invocation_create_response import ( + AuthAgentInvocationCreateResponse as AuthAgentInvocationCreateResponse, +) diff --git a/src/kernel/types/agents/auth/__init__.py b/src/kernel/types/agents/auth/__init__.py index bfbd280..0296883 100644 --- a/src/kernel/types/agents/auth/__init__.py +++ b/src/kernel/types/agents/auth/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +from .invocation_create_params import InvocationCreateParams as InvocationCreateParams from .invocation_submit_params import InvocationSubmitParams as InvocationSubmitParams from .invocation_discover_params import InvocationDiscoverParams as InvocationDiscoverParams from .invocation_exchange_params import InvocationExchangeParams as InvocationExchangeParams diff --git a/src/kernel/types/agents/auth/invocation_create_params.py b/src/kernel/types/agents/auth/invocation_create_params.py new file mode 100644 index 0000000..b3de645 --- /dev/null +++ b/src/kernel/types/agents/auth/invocation_create_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["InvocationCreateParams"] + + +class InvocationCreateParams(TypedDict, total=False): + auth_agent_id: Required[str] + """ID of the auth agent to create an invocation for""" diff --git a/src/kernel/types/agents/agent_auth_start_response.py b/src/kernel/types/agents/auth_agent_invocation_create_response.py similarity index 69% rename from src/kernel/types/agents/agent_auth_start_response.py rename to src/kernel/types/agents/auth_agent_invocation_create_response.py index 3287ba0..0283c35 100644 --- a/src/kernel/types/agents/agent_auth_start_response.py +++ b/src/kernel/types/agents/auth_agent_invocation_create_response.py @@ -4,13 +4,10 @@ from ..._models import BaseModel -__all__ = ["AgentAuthStartResponse"] +__all__ = ["AuthAgentInvocationCreateResponse"] -class AgentAuthStartResponse(BaseModel): - auth_agent_id: str - """Unique identifier for the auth agent managing this domain/profile""" - +class AuthAgentInvocationCreateResponse(BaseModel): expires_at: datetime """When the handoff code expires""" diff --git a/src/kernel/types/agents/auth_start_params.py b/src/kernel/types/agents/auth_create_params.py similarity index 61% rename from src/kernel/types/agents/auth_start_params.py rename to src/kernel/types/agents/auth_create_params.py index 9c9fb35..fe57730 100644 --- a/src/kernel/types/agents/auth_start_params.py +++ b/src/kernel/types/agents/auth_create_params.py @@ -4,24 +4,21 @@ from typing_extensions import Required, TypedDict -__all__ = ["AuthStartParams", "Proxy"] +__all__ = ["AuthCreateParams", "Proxy"] -class AuthStartParams(TypedDict, total=False): +class AuthCreateParams(TypedDict, total=False): profile_name: Required[str] - """Name of the profile to use for this flow""" + """Name of the profile to use for this auth agent""" target_domain: Required[str] """Target domain for authentication""" - app_logo_url: str - """Optional logo URL for the application""" - login_url: str """Optional login page URL. - If provided, will be stored on the agent and used to skip Phase 1 discovery in - future invocations. + If provided, will be stored on the agent and used to skip discovery in future + invocations. """ proxy: Proxy diff --git a/src/kernel/types/agents/auth_list_params.py b/src/kernel/types/agents/auth_list_params.py new file mode 100644 index 0000000..a4b2ffc --- /dev/null +++ b/src/kernel/types/agents/auth_list_params.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["AuthListParams"] + + +class AuthListParams(TypedDict, total=False): + limit: int + """Maximum number of results to return""" + + offset: int + """Number of results to skip""" + + profile_name: str + """Filter by profile name""" + + target_domain: str + """Filter by target domain""" diff --git a/tests/api_resources/agents/auth/test_invocations.py b/tests/api_resources/agents/auth/test_invocations.py index e9c3d8f..7957caf 100644 --- a/tests/api_resources/agents/auth/test_invocations.py +++ b/tests/api_resources/agents/auth/test_invocations.py @@ -9,7 +9,12 @@ from kernel import Kernel, AsyncKernel from tests.utils import assert_matches_type -from kernel.types.agents import AgentAuthSubmitResponse, AgentAuthDiscoverResponse, AgentAuthInvocationResponse +from kernel.types.agents import ( + AgentAuthSubmitResponse, + AgentAuthDiscoverResponse, + AgentAuthInvocationResponse, + AuthAgentInvocationCreateResponse, +) from kernel.types.agents.auth import ( InvocationExchangeResponse, ) @@ -20,6 +25,40 @@ class TestInvocations: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: Kernel) -> None: + invocation = client.agents.auth.invocations.create( + auth_agent_id="abc123xyz", + ) + assert_matches_type(AuthAgentInvocationCreateResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Kernel) -> None: + response = client.agents.auth.invocations.with_raw_response.create( + auth_agent_id="abc123xyz", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = response.parse() + assert_matches_type(AuthAgentInvocationCreateResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Kernel) -> None: + with client.agents.auth.invocations.with_streaming_response.create( + auth_agent_id="abc123xyz", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invocation = response.parse() + assert_matches_type(AuthAgentInvocationCreateResponse, invocation, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Kernel) -> None: @@ -223,6 +262,40 @@ class TestAsyncInvocations: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncKernel) -> None: + invocation = await async_client.agents.auth.invocations.create( + auth_agent_id="abc123xyz", + ) + assert_matches_type(AuthAgentInvocationCreateResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncKernel) -> None: + response = await async_client.agents.auth.invocations.with_raw_response.create( + auth_agent_id="abc123xyz", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = await response.parse() + assert_matches_type(AuthAgentInvocationCreateResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncKernel) -> None: + async with async_client.agents.auth.invocations.with_streaming_response.create( + auth_agent_id="abc123xyz", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + invocation = await response.parse() + assert_matches_type(AuthAgentInvocationCreateResponse, invocation, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncKernel) -> None: diff --git a/tests/api_resources/agents/test_auth.py b/tests/api_resources/agents/test_auth.py index 0dc190d..5115f90 100644 --- a/tests/api_resources/agents/test_auth.py +++ b/tests/api_resources/agents/test_auth.py @@ -9,7 +9,8 @@ from kernel import Kernel, AsyncKernel from tests.utils import assert_matches_type -from kernel.types.agents import AuthAgent, AgentAuthStartResponse +from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination +from kernel.types.agents import AuthAgent base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -17,6 +18,54 @@ class TestAuth: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: Kernel) -> None: + auth = client.agents.auth.create( + profile_name="user-123", + target_domain="netflix.com", + ) + assert_matches_type(AuthAgent, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Kernel) -> None: + auth = client.agents.auth.create( + profile_name="user-123", + target_domain="netflix.com", + login_url="https://netflix.com/login", + proxy={"proxy_id": "proxy_id"}, + ) + assert_matches_type(AuthAgent, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Kernel) -> None: + response = client.agents.auth.with_raw_response.create( + profile_name="user-123", + target_domain="netflix.com", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + auth = response.parse() + assert_matches_type(AuthAgent, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Kernel) -> None: + with client.agents.auth.with_streaming_response.create( + profile_name="user-123", + target_domain="netflix.com", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + auth = response.parse() + assert_matches_type(AuthAgent, auth, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: Kernel) -> None: @@ -61,50 +110,40 @@ def test_path_params_retrieve(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_method_start(self, client: Kernel) -> None: - auth = client.agents.auth.start( - profile_name="auth-abc123", - target_domain="doordash.com", - ) - assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + def test_method_list(self, client: Kernel) -> None: + auth = client.agents.auth.list() + assert_matches_type(SyncOffsetPagination[AuthAgent], auth, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_method_start_with_all_params(self, client: Kernel) -> None: - auth = client.agents.auth.start( - profile_name="auth-abc123", - target_domain="doordash.com", - app_logo_url="https://example.com/logo.png", - login_url="https://doordash.com/account/login", - proxy={"proxy_id": "proxy_id"}, + def test_method_list_with_all_params(self, client: Kernel) -> None: + auth = client.agents.auth.list( + limit=100, + offset=0, + profile_name="profile_name", + target_domain="target_domain", ) - assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + assert_matches_type(SyncOffsetPagination[AuthAgent], auth, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_raw_response_start(self, client: Kernel) -> None: - response = client.agents.auth.with_raw_response.start( - profile_name="auth-abc123", - target_domain="doordash.com", - ) + def test_raw_response_list(self, client: Kernel) -> None: + response = client.agents.auth.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" auth = response.parse() - assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + assert_matches_type(SyncOffsetPagination[AuthAgent], auth, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_streaming_response_start(self, client: Kernel) -> None: - with client.agents.auth.with_streaming_response.start( - profile_name="auth-abc123", - target_domain="doordash.com", - ) as response: + def test_streaming_response_list(self, client: Kernel) -> None: + with client.agents.auth.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" auth = response.parse() - assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + assert_matches_type(SyncOffsetPagination[AuthAgent], auth, path=["response"]) assert cast(Any, response.is_closed) is True @@ -114,6 +153,54 @@ class TestAsyncAuth: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncKernel) -> None: + auth = await async_client.agents.auth.create( + profile_name="user-123", + target_domain="netflix.com", + ) + assert_matches_type(AuthAgent, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None: + auth = await async_client.agents.auth.create( + profile_name="user-123", + target_domain="netflix.com", + login_url="https://netflix.com/login", + proxy={"proxy_id": "proxy_id"}, + ) + assert_matches_type(AuthAgent, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncKernel) -> None: + response = await async_client.agents.auth.with_raw_response.create( + profile_name="user-123", + target_domain="netflix.com", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + auth = await response.parse() + assert_matches_type(AuthAgent, auth, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncKernel) -> None: + async with async_client.agents.auth.with_streaming_response.create( + profile_name="user-123", + target_domain="netflix.com", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + auth = await response.parse() + assert_matches_type(AuthAgent, auth, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncKernel) -> None: @@ -158,49 +245,39 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_method_start(self, async_client: AsyncKernel) -> None: - auth = await async_client.agents.auth.start( - profile_name="auth-abc123", - target_domain="doordash.com", - ) - assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + async def test_method_list(self, async_client: AsyncKernel) -> None: + auth = await async_client.agents.auth.list() + assert_matches_type(AsyncOffsetPagination[AuthAgent], auth, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_method_start_with_all_params(self, async_client: AsyncKernel) -> None: - auth = await async_client.agents.auth.start( - profile_name="auth-abc123", - target_domain="doordash.com", - app_logo_url="https://example.com/logo.png", - login_url="https://doordash.com/account/login", - proxy={"proxy_id": "proxy_id"}, + async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None: + auth = await async_client.agents.auth.list( + limit=100, + offset=0, + profile_name="profile_name", + target_domain="target_domain", ) - assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + assert_matches_type(AsyncOffsetPagination[AuthAgent], auth, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_raw_response_start(self, async_client: AsyncKernel) -> None: - response = await async_client.agents.auth.with_raw_response.start( - profile_name="auth-abc123", - target_domain="doordash.com", - ) + async def test_raw_response_list(self, async_client: AsyncKernel) -> None: + response = await async_client.agents.auth.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" auth = await response.parse() - assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + assert_matches_type(AsyncOffsetPagination[AuthAgent], auth, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_streaming_response_start(self, async_client: AsyncKernel) -> None: - async with async_client.agents.auth.with_streaming_response.start( - profile_name="auth-abc123", - target_domain="doordash.com", - ) as response: + async def test_streaming_response_list(self, async_client: AsyncKernel) -> None: + async with async_client.agents.auth.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" auth = await response.parse() - assert_matches_type(AgentAuthStartResponse, auth, path=["response"]) + assert_matches_type(AsyncOffsetPagination[AuthAgent], auth, path=["response"]) assert cast(Any, response.is_closed) is True From c8d571c785a4488549701aad525357a9fabec69f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 03:42:24 +0000 Subject: [PATCH 5/8] fix(types): allow pyright to infer TypedDict types within SequenceNotStr --- src/kernel/_types.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/kernel/_types.py b/src/kernel/_types.py index 2c1d83b..275ffbb 100644 --- a/src/kernel/_types.py +++ b/src/kernel/_types.py @@ -243,6 +243,9 @@ class HttpxSendArgs(TypedDict, total=False): if TYPE_CHECKING: # This works because str.__contains__ does not accept object (either in typeshed or at runtime) # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + # + # Note: index() and count() methods are intentionally omitted to allow pyright to properly + # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. class SequenceNotStr(Protocol[_T_co]): @overload def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... @@ -251,8 +254,6 @@ def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... def __contains__(self, value: object, /) -> bool: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[_T_co]: ... - def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... - def count(self, value: Any, /) -> int: ... def __reversed__(self) -> Iterator[_T_co]: ... else: # just point this to a normal `Sequence` at runtime to avoid having to special case From 75fa7cb7b954f8a68cc5eb74ad3196b350e8f9dd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 03:44:08 +0000 Subject: [PATCH 6/8] chore: add missing docstrings --- .../types/agents/agent_auth_discover_response.py | 2 ++ .../types/agents/agent_auth_invocation_response.py | 2 ++ .../types/agents/agent_auth_submit_response.py | 2 ++ .../agents/auth/invocation_exchange_response.py | 2 ++ src/kernel/types/agents/auth_agent.py | 4 ++++ .../agents/auth_agent_invocation_create_response.py | 2 ++ src/kernel/types/agents/auth_create_params.py | 2 ++ src/kernel/types/agents/discovered_field.py | 2 ++ src/kernel/types/app_list_response.py | 2 ++ src/kernel/types/browser_persistence.py | 2 ++ src/kernel/types/browser_persistence_param.py | 2 ++ src/kernel/types/browser_pool.py | 2 ++ src/kernel/types/browser_pool_request.py | 5 +++++ .../computer_set_cursor_visibility_response.py | 2 ++ .../types/browsers/fs/watch_events_response.py | 2 ++ .../types/browsers/playwright_execute_response.py | 2 ++ src/kernel/types/browsers/process_exec_response.py | 2 ++ src/kernel/types/browsers/process_kill_response.py | 2 ++ src/kernel/types/browsers/process_spawn_response.py | 2 ++ src/kernel/types/browsers/process_status_response.py | 2 ++ src/kernel/types/browsers/process_stdin_response.py | 2 ++ .../types/browsers/process_stdout_stream_response.py | 2 ++ src/kernel/types/browsers/replay_list_response.py | 2 ++ src/kernel/types/browsers/replay_start_response.py | 2 ++ src/kernel/types/deployment_create_params.py | 4 ++++ src/kernel/types/deployment_create_response.py | 2 ++ src/kernel/types/deployment_follow_response.py | 2 ++ src/kernel/types/deployment_list_response.py | 2 ++ src/kernel/types/deployment_retrieve_response.py | 2 ++ src/kernel/types/deployment_state_event.py | 4 ++++ src/kernel/types/extension_list_response.py | 2 ++ src/kernel/types/extension_upload_response.py | 2 ++ src/kernel/types/invocation_state_event.py | 2 ++ src/kernel/types/profile.py | 2 ++ src/kernel/types/proxy_create_params.py | 10 ++++++++++ src/kernel/types/proxy_create_response.py | 12 ++++++++++++ src/kernel/types/proxy_list_response.py | 12 ++++++++++++ src/kernel/types/proxy_retrieve_response.py | 12 ++++++++++++ src/kernel/types/shared/app_action.py | 2 ++ src/kernel/types/shared/browser_extension.py | 5 +++++ src/kernel/types/shared/browser_profile.py | 6 ++++++ src/kernel/types/shared/browser_viewport.py | 9 +++++++++ src/kernel/types/shared/error_event.py | 2 ++ src/kernel/types/shared/heartbeat_event.py | 2 ++ src/kernel/types/shared/log_event.py | 2 ++ src/kernel/types/shared_params/browser_extension.py | 5 +++++ src/kernel/types/shared_params/browser_profile.py | 6 ++++++ src/kernel/types/shared_params/browser_viewport.py | 9 +++++++++ 48 files changed, 171 insertions(+) diff --git a/src/kernel/types/agents/agent_auth_discover_response.py b/src/kernel/types/agents/agent_auth_discover_response.py index 000bdec..5e411dc 100644 --- a/src/kernel/types/agents/agent_auth_discover_response.py +++ b/src/kernel/types/agents/agent_auth_discover_response.py @@ -9,6 +9,8 @@ class AgentAuthDiscoverResponse(BaseModel): + """Response from discover endpoint matching AuthBlueprint schema""" + success: bool """Whether discovery succeeded""" diff --git a/src/kernel/types/agents/agent_auth_invocation_response.py b/src/kernel/types/agents/agent_auth_invocation_response.py index 82b5f80..02b5ecf 100644 --- a/src/kernel/types/agents/agent_auth_invocation_response.py +++ b/src/kernel/types/agents/agent_auth_invocation_response.py @@ -9,6 +9,8 @@ class AgentAuthInvocationResponse(BaseModel): + """Response from get invocation endpoint""" + app_name: str """App name (org name at time of invocation creation)""" diff --git a/src/kernel/types/agents/agent_auth_submit_response.py b/src/kernel/types/agents/agent_auth_submit_response.py index c57002f..5ca9578 100644 --- a/src/kernel/types/agents/agent_auth_submit_response.py +++ b/src/kernel/types/agents/agent_auth_submit_response.py @@ -9,6 +9,8 @@ class AgentAuthSubmitResponse(BaseModel): + """Response from submit endpoint matching SubmitResult schema""" + success: bool """Whether submission succeeded""" diff --git a/src/kernel/types/agents/auth/invocation_exchange_response.py b/src/kernel/types/agents/auth/invocation_exchange_response.py index 91b74ce..710d9c3 100644 --- a/src/kernel/types/agents/auth/invocation_exchange_response.py +++ b/src/kernel/types/agents/auth/invocation_exchange_response.py @@ -6,6 +6,8 @@ class InvocationExchangeResponse(BaseModel): + """Response from exchange endpoint""" + invocation_id: str """Invocation ID""" diff --git a/src/kernel/types/agents/auth_agent.py b/src/kernel/types/agents/auth_agent.py index 8671f97..ff9f5e9 100644 --- a/src/kernel/types/agents/auth_agent.py +++ b/src/kernel/types/agents/auth_agent.py @@ -8,6 +8,10 @@ class AuthAgent(BaseModel): + """ + An auth agent that manages authentication for a specific domain and profile combination + """ + id: str """Unique identifier for the auth agent""" diff --git a/src/kernel/types/agents/auth_agent_invocation_create_response.py b/src/kernel/types/agents/auth_agent_invocation_create_response.py index 0283c35..baa80e2 100644 --- a/src/kernel/types/agents/auth_agent_invocation_create_response.py +++ b/src/kernel/types/agents/auth_agent_invocation_create_response.py @@ -8,6 +8,8 @@ class AuthAgentInvocationCreateResponse(BaseModel): + """Response from creating an auth agent invocation""" + expires_at: datetime """When the handoff code expires""" diff --git a/src/kernel/types/agents/auth_create_params.py b/src/kernel/types/agents/auth_create_params.py index fe57730..7869925 100644 --- a/src/kernel/types/agents/auth_create_params.py +++ b/src/kernel/types/agents/auth_create_params.py @@ -26,5 +26,7 @@ class AuthCreateParams(TypedDict, total=False): class Proxy(TypedDict, total=False): + """Optional proxy configuration""" + proxy_id: str """ID of the proxy to use""" diff --git a/src/kernel/types/agents/discovered_field.py b/src/kernel/types/agents/discovered_field.py index d1b9dc9..0c6715c 100644 --- a/src/kernel/types/agents/discovered_field.py +++ b/src/kernel/types/agents/discovered_field.py @@ -9,6 +9,8 @@ class DiscoveredField(BaseModel): + """A discovered form field""" + label: str """Field label""" diff --git a/src/kernel/types/app_list_response.py b/src/kernel/types/app_list_response.py index 56a2d4b..338f506 100644 --- a/src/kernel/types/app_list_response.py +++ b/src/kernel/types/app_list_response.py @@ -10,6 +10,8 @@ class AppListResponse(BaseModel): + """Summary of an application version.""" + id: str """Unique identifier for the app version""" diff --git a/src/kernel/types/browser_persistence.py b/src/kernel/types/browser_persistence.py index 5c362ee..381d630 100644 --- a/src/kernel/types/browser_persistence.py +++ b/src/kernel/types/browser_persistence.py @@ -6,5 +6,7 @@ class BrowserPersistence(BaseModel): + """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead.""" + id: str """DEPRECATED: Unique identifier for the persistent browser session.""" diff --git a/src/kernel/types/browser_persistence_param.py b/src/kernel/types/browser_persistence_param.py index bbd9e48..6109abf 100644 --- a/src/kernel/types/browser_persistence_param.py +++ b/src/kernel/types/browser_persistence_param.py @@ -8,5 +8,7 @@ class BrowserPersistenceParam(TypedDict, total=False): + """DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead.""" + id: Required[str] """DEPRECATED: Unique identifier for the persistent browser session.""" diff --git a/src/kernel/types/browser_pool.py b/src/kernel/types/browser_pool.py index 5fd30dc..ddd3d9f 100644 --- a/src/kernel/types/browser_pool.py +++ b/src/kernel/types/browser_pool.py @@ -10,6 +10,8 @@ class BrowserPool(BaseModel): + """A browser pool containing multiple identically configured browsers.""" + id: str """Unique identifier for the browser pool""" diff --git a/src/kernel/types/browser_pool_request.py b/src/kernel/types/browser_pool_request.py index c54fad4..a392b3f 100644 --- a/src/kernel/types/browser_pool_request.py +++ b/src/kernel/types/browser_pool_request.py @@ -11,6 +11,11 @@ class BrowserPoolRequest(BaseModel): + """Parameters for creating a browser pool. + + All browsers in the pool will be created with the same configuration. + """ + size: int """Number of browsers to create in the pool""" diff --git a/src/kernel/types/browsers/computer_set_cursor_visibility_response.py b/src/kernel/types/browsers/computer_set_cursor_visibility_response.py index c82302e..0e07023 100644 --- a/src/kernel/types/browsers/computer_set_cursor_visibility_response.py +++ b/src/kernel/types/browsers/computer_set_cursor_visibility_response.py @@ -6,5 +6,7 @@ class ComputerSetCursorVisibilityResponse(BaseModel): + """Generic OK response.""" + ok: bool """Indicates success.""" diff --git a/src/kernel/types/browsers/fs/watch_events_response.py b/src/kernel/types/browsers/fs/watch_events_response.py index 8df2f50..5778a30 100644 --- a/src/kernel/types/browsers/fs/watch_events_response.py +++ b/src/kernel/types/browsers/fs/watch_events_response.py @@ -9,6 +9,8 @@ class WatchEventsResponse(BaseModel): + """Filesystem change event.""" + path: str """Absolute path of the file or directory.""" diff --git a/src/kernel/types/browsers/playwright_execute_response.py b/src/kernel/types/browsers/playwright_execute_response.py index a805ba8..d53080d 100644 --- a/src/kernel/types/browsers/playwright_execute_response.py +++ b/src/kernel/types/browsers/playwright_execute_response.py @@ -8,6 +8,8 @@ class PlaywrightExecuteResponse(BaseModel): + """Result of Playwright code execution""" + success: bool """Whether the code executed successfully""" diff --git a/src/kernel/types/browsers/process_exec_response.py b/src/kernel/types/browsers/process_exec_response.py index 02588de..a5e4b77 100644 --- a/src/kernel/types/browsers/process_exec_response.py +++ b/src/kernel/types/browsers/process_exec_response.py @@ -8,6 +8,8 @@ class ProcessExecResponse(BaseModel): + """Result of a synchronous command execution.""" + duration_ms: Optional[int] = None """Execution duration in milliseconds.""" diff --git a/src/kernel/types/browsers/process_kill_response.py b/src/kernel/types/browsers/process_kill_response.py index ed128a7..6706e88 100644 --- a/src/kernel/types/browsers/process_kill_response.py +++ b/src/kernel/types/browsers/process_kill_response.py @@ -6,5 +6,7 @@ class ProcessKillResponse(BaseModel): + """Generic OK response.""" + ok: bool """Indicates success.""" diff --git a/src/kernel/types/browsers/process_spawn_response.py b/src/kernel/types/browsers/process_spawn_response.py index 23444da..0cda64d 100644 --- a/src/kernel/types/browsers/process_spawn_response.py +++ b/src/kernel/types/browsers/process_spawn_response.py @@ -9,6 +9,8 @@ class ProcessSpawnResponse(BaseModel): + """Information about a spawned process.""" + pid: Optional[int] = None """OS process ID.""" diff --git a/src/kernel/types/browsers/process_status_response.py b/src/kernel/types/browsers/process_status_response.py index 67626fe..91c7724 100644 --- a/src/kernel/types/browsers/process_status_response.py +++ b/src/kernel/types/browsers/process_status_response.py @@ -9,6 +9,8 @@ class ProcessStatusResponse(BaseModel): + """Current status of a process.""" + cpu_pct: Optional[float] = None """Estimated CPU usage percentage.""" diff --git a/src/kernel/types/browsers/process_stdin_response.py b/src/kernel/types/browsers/process_stdin_response.py index d137a96..be3c798 100644 --- a/src/kernel/types/browsers/process_stdin_response.py +++ b/src/kernel/types/browsers/process_stdin_response.py @@ -8,5 +8,7 @@ class ProcessStdinResponse(BaseModel): + """Result of writing to stdin.""" + written_bytes: Optional[int] = None """Number of bytes written.""" diff --git a/src/kernel/types/browsers/process_stdout_stream_response.py b/src/kernel/types/browsers/process_stdout_stream_response.py index 0b1d0a8..6e911f5 100644 --- a/src/kernel/types/browsers/process_stdout_stream_response.py +++ b/src/kernel/types/browsers/process_stdout_stream_response.py @@ -9,6 +9,8 @@ class ProcessStdoutStreamResponse(BaseModel): + """SSE payload representing process output or lifecycle events.""" + data_b64: Optional[str] = None """Base64-encoded data from the process stream.""" diff --git a/src/kernel/types/browsers/replay_list_response.py b/src/kernel/types/browsers/replay_list_response.py index f53dd4d..8cf9d54 100644 --- a/src/kernel/types/browsers/replay_list_response.py +++ b/src/kernel/types/browsers/replay_list_response.py @@ -10,6 +10,8 @@ class ReplayListResponseItem(BaseModel): + """Information about a browser replay recording.""" + replay_id: str """Unique identifier for the replay recording.""" diff --git a/src/kernel/types/browsers/replay_start_response.py b/src/kernel/types/browsers/replay_start_response.py index dd837d5..ac4130b 100644 --- a/src/kernel/types/browsers/replay_start_response.py +++ b/src/kernel/types/browsers/replay_start_response.py @@ -9,6 +9,8 @@ class ReplayStartResponse(BaseModel): + """Information about a browser replay recording.""" + replay_id: str """Unique identifier for the replay recording.""" diff --git a/src/kernel/types/deployment_create_params.py b/src/kernel/types/deployment_create_params.py index 16eb570..84d3d87 100644 --- a/src/kernel/types/deployment_create_params.py +++ b/src/kernel/types/deployment_create_params.py @@ -37,6 +37,8 @@ class DeploymentCreateParams(TypedDict, total=False): class SourceAuth(TypedDict, total=False): + """Authentication for private repositories.""" + token: Required[str] """GitHub PAT or installation access token""" @@ -45,6 +47,8 @@ class SourceAuth(TypedDict, total=False): class Source(TypedDict, total=False): + """Source from which to fetch application code.""" + entrypoint: Required[str] """Relative path to the application entrypoint within the selected path.""" diff --git a/src/kernel/types/deployment_create_response.py b/src/kernel/types/deployment_create_response.py index c14bf27..5746c97 100644 --- a/src/kernel/types/deployment_create_response.py +++ b/src/kernel/types/deployment_create_response.py @@ -10,6 +10,8 @@ class DeploymentCreateResponse(BaseModel): + """Deployment record information.""" + id: str """Unique identifier for the deployment""" diff --git a/src/kernel/types/deployment_follow_response.py b/src/kernel/types/deployment_follow_response.py index ca3c512..d6de222 100644 --- a/src/kernel/types/deployment_follow_response.py +++ b/src/kernel/types/deployment_follow_response.py @@ -16,6 +16,8 @@ class AppVersionSummaryEvent(BaseModel): + """Summary of an application version.""" + id: str """Unique identifier for the app version""" diff --git a/src/kernel/types/deployment_list_response.py b/src/kernel/types/deployment_list_response.py index d22b007..d7719d4 100644 --- a/src/kernel/types/deployment_list_response.py +++ b/src/kernel/types/deployment_list_response.py @@ -10,6 +10,8 @@ class DeploymentListResponse(BaseModel): + """Deployment record information.""" + id: str """Unique identifier for the deployment""" diff --git a/src/kernel/types/deployment_retrieve_response.py b/src/kernel/types/deployment_retrieve_response.py index 28c0d4b..3601c86 100644 --- a/src/kernel/types/deployment_retrieve_response.py +++ b/src/kernel/types/deployment_retrieve_response.py @@ -10,6 +10,8 @@ class DeploymentRetrieveResponse(BaseModel): + """Deployment record information.""" + id: str """Unique identifier for the deployment""" diff --git a/src/kernel/types/deployment_state_event.py b/src/kernel/types/deployment_state_event.py index 572d51b..cc221c7 100644 --- a/src/kernel/types/deployment_state_event.py +++ b/src/kernel/types/deployment_state_event.py @@ -10,6 +10,8 @@ class Deployment(BaseModel): + """Deployment record information.""" + id: str """Unique identifier for the deployment""" @@ -36,6 +38,8 @@ class Deployment(BaseModel): class DeploymentStateEvent(BaseModel): + """An event representing the current state of a deployment.""" + deployment: Deployment """Deployment record information.""" diff --git a/src/kernel/types/extension_list_response.py b/src/kernel/types/extension_list_response.py index c8c99e7..79a5c99 100644 --- a/src/kernel/types/extension_list_response.py +++ b/src/kernel/types/extension_list_response.py @@ -10,6 +10,8 @@ class ExtensionListResponseItem(BaseModel): + """A browser extension uploaded to Kernel.""" + id: str """Unique identifier for the extension""" diff --git a/src/kernel/types/extension_upload_response.py b/src/kernel/types/extension_upload_response.py index 373e886..1b3be22 100644 --- a/src/kernel/types/extension_upload_response.py +++ b/src/kernel/types/extension_upload_response.py @@ -9,6 +9,8 @@ class ExtensionUploadResponse(BaseModel): + """A browser extension uploaded to Kernel.""" + id: str """Unique identifier for the extension""" diff --git a/src/kernel/types/invocation_state_event.py b/src/kernel/types/invocation_state_event.py index 48a2fa3..f32bf8e 100644 --- a/src/kernel/types/invocation_state_event.py +++ b/src/kernel/types/invocation_state_event.py @@ -51,6 +51,8 @@ class Invocation(BaseModel): class InvocationStateEvent(BaseModel): + """An event representing the current state of an invocation.""" + event: Literal["invocation_state"] """Event type identifier (always "invocation_state").""" diff --git a/src/kernel/types/profile.py b/src/kernel/types/profile.py index 3ec5890..e141aa0 100644 --- a/src/kernel/types/profile.py +++ b/src/kernel/types/profile.py @@ -9,6 +9,8 @@ class Profile(BaseModel): + """Browser profile metadata.""" + id: str """Unique identifier for the profile""" diff --git a/src/kernel/types/proxy_create_params.py b/src/kernel/types/proxy_create_params.py index 485df60..0a3536f 100644 --- a/src/kernel/types/proxy_create_params.py +++ b/src/kernel/types/proxy_create_params.py @@ -35,16 +35,22 @@ class ProxyCreateParams(TypedDict, total=False): class ConfigDatacenterProxyConfig(TypedDict, total=False): + """Configuration for a datacenter proxy.""" + country: str """ISO 3166 country code. Defaults to US if not provided.""" class ConfigIspProxyConfig(TypedDict, total=False): + """Configuration for an ISP proxy.""" + country: str """ISO 3166 country code. Defaults to US if not provided.""" class ConfigResidentialProxyConfig(TypedDict, total=False): + """Configuration for residential proxies.""" + asn: str """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html""" @@ -68,6 +74,8 @@ class ConfigResidentialProxyConfig(TypedDict, total=False): class ConfigMobileProxyConfig(TypedDict, total=False): + """Configuration for mobile proxies.""" + asn: str """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html""" @@ -150,6 +158,8 @@ class ConfigMobileProxyConfig(TypedDict, total=False): class ConfigCreateCustomProxyConfig(TypedDict, total=False): + """Configuration for a custom proxy (e.g., private proxy server).""" + host: Required[str] """Proxy host address or IP.""" diff --git a/src/kernel/types/proxy_create_response.py b/src/kernel/types/proxy_create_response.py index 6ec2f7f..dc474ab 100644 --- a/src/kernel/types/proxy_create_response.py +++ b/src/kernel/types/proxy_create_response.py @@ -18,16 +18,22 @@ class ConfigDatacenterProxyConfig(BaseModel): + """Configuration for a datacenter proxy.""" + country: Optional[str] = None """ISO 3166 country code. Defaults to US if not provided.""" class ConfigIspProxyConfig(BaseModel): + """Configuration for an ISP proxy.""" + country: Optional[str] = None """ISO 3166 country code. Defaults to US if not provided.""" class ConfigResidentialProxyConfig(BaseModel): + """Configuration for residential proxies.""" + asn: Optional[str] = None """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html""" @@ -51,6 +57,8 @@ class ConfigResidentialProxyConfig(BaseModel): class ConfigMobileProxyConfig(BaseModel): + """Configuration for mobile proxies.""" + asn: Optional[str] = None """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html""" @@ -135,6 +143,8 @@ class ConfigMobileProxyConfig(BaseModel): class ConfigCustomProxyConfig(BaseModel): + """Configuration for a custom proxy (e.g., private proxy server).""" + host: str """Proxy host address or IP.""" @@ -158,6 +168,8 @@ class ConfigCustomProxyConfig(BaseModel): class ProxyCreateResponse(BaseModel): + """Configuration for routing traffic through a proxy.""" + type: Literal["datacenter", "isp", "residential", "mobile", "custom"] """Proxy type to use. diff --git a/src/kernel/types/proxy_list_response.py b/src/kernel/types/proxy_list_response.py index e4abb0d..08c846f 100644 --- a/src/kernel/types/proxy_list_response.py +++ b/src/kernel/types/proxy_list_response.py @@ -19,16 +19,22 @@ class ProxyListResponseItemConfigDatacenterProxyConfig(BaseModel): + """Configuration for a datacenter proxy.""" + country: Optional[str] = None """ISO 3166 country code. Defaults to US if not provided.""" class ProxyListResponseItemConfigIspProxyConfig(BaseModel): + """Configuration for an ISP proxy.""" + country: Optional[str] = None """ISO 3166 country code. Defaults to US if not provided.""" class ProxyListResponseItemConfigResidentialProxyConfig(BaseModel): + """Configuration for residential proxies.""" + asn: Optional[str] = None """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html""" @@ -52,6 +58,8 @@ class ProxyListResponseItemConfigResidentialProxyConfig(BaseModel): class ProxyListResponseItemConfigMobileProxyConfig(BaseModel): + """Configuration for mobile proxies.""" + asn: Optional[str] = None """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html""" @@ -136,6 +144,8 @@ class ProxyListResponseItemConfigMobileProxyConfig(BaseModel): class ProxyListResponseItemConfigCustomProxyConfig(BaseModel): + """Configuration for a custom proxy (e.g., private proxy server).""" + host: str """Proxy host address or IP.""" @@ -159,6 +169,8 @@ class ProxyListResponseItemConfigCustomProxyConfig(BaseModel): class ProxyListResponseItem(BaseModel): + """Configuration for routing traffic through a proxy.""" + type: Literal["datacenter", "isp", "residential", "mobile", "custom"] """Proxy type to use. diff --git a/src/kernel/types/proxy_retrieve_response.py b/src/kernel/types/proxy_retrieve_response.py index 5262fc4..24c7b96 100644 --- a/src/kernel/types/proxy_retrieve_response.py +++ b/src/kernel/types/proxy_retrieve_response.py @@ -18,16 +18,22 @@ class ConfigDatacenterProxyConfig(BaseModel): + """Configuration for a datacenter proxy.""" + country: Optional[str] = None """ISO 3166 country code. Defaults to US if not provided.""" class ConfigIspProxyConfig(BaseModel): + """Configuration for an ISP proxy.""" + country: Optional[str] = None """ISO 3166 country code. Defaults to US if not provided.""" class ConfigResidentialProxyConfig(BaseModel): + """Configuration for residential proxies.""" + asn: Optional[str] = None """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html""" @@ -51,6 +57,8 @@ class ConfigResidentialProxyConfig(BaseModel): class ConfigMobileProxyConfig(BaseModel): + """Configuration for mobile proxies.""" + asn: Optional[str] = None """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html""" @@ -135,6 +143,8 @@ class ConfigMobileProxyConfig(BaseModel): class ConfigCustomProxyConfig(BaseModel): + """Configuration for a custom proxy (e.g., private proxy server).""" + host: str """Proxy host address or IP.""" @@ -158,6 +168,8 @@ class ConfigCustomProxyConfig(BaseModel): class ProxyRetrieveResponse(BaseModel): + """Configuration for routing traffic through a proxy.""" + type: Literal["datacenter", "isp", "residential", "mobile", "custom"] """Proxy type to use. diff --git a/src/kernel/types/shared/app_action.py b/src/kernel/types/shared/app_action.py index 3d71136..1babce1 100644 --- a/src/kernel/types/shared/app_action.py +++ b/src/kernel/types/shared/app_action.py @@ -6,5 +6,7 @@ class AppAction(BaseModel): + """An action available on the app""" + name: str """Name of the action""" diff --git a/src/kernel/types/shared/browser_extension.py b/src/kernel/types/shared/browser_extension.py index 7bc1a5f..a91d2dc 100644 --- a/src/kernel/types/shared/browser_extension.py +++ b/src/kernel/types/shared/browser_extension.py @@ -8,6 +8,11 @@ class BrowserExtension(BaseModel): + """Extension selection for the browser session. + + Provide either id or name of an extension uploaded to Kernel. + """ + id: Optional[str] = None """Extension ID to load for this browser session""" diff --git a/src/kernel/types/shared/browser_profile.py b/src/kernel/types/shared/browser_profile.py index 5f790cc..4aadc31 100644 --- a/src/kernel/types/shared/browser_profile.py +++ b/src/kernel/types/shared/browser_profile.py @@ -8,6 +8,12 @@ class BrowserProfile(BaseModel): + """Profile selection for the browser session. + + Provide either id or name. If specified, the + matching profile will be loaded into the browser session. Profiles must be created beforehand. + """ + id: Optional[str] = None """Profile ID to load for this browser session""" diff --git a/src/kernel/types/shared/browser_viewport.py b/src/kernel/types/shared/browser_viewport.py index abffcc2..ab8f427 100644 --- a/src/kernel/types/shared/browser_viewport.py +++ b/src/kernel/types/shared/browser_viewport.py @@ -8,6 +8,15 @@ class BrowserViewport(BaseModel): + """Initial browser window size in pixels with optional refresh rate. + + If omitted, image defaults apply (1920x1080@25). + Only specific viewport configurations are supported. The server will reject unsupported combinations. + Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60 + If refresh_rate is not provided, it will be automatically determined from the width and height if they match a supported configuration exactly. + Note: Higher resolutions may affect the responsiveness of live view browser + """ + height: int """Browser window height in pixels.""" diff --git a/src/kernel/types/shared/error_event.py b/src/kernel/types/shared/error_event.py index 0041b89..35175f5 100644 --- a/src/kernel/types/shared/error_event.py +++ b/src/kernel/types/shared/error_event.py @@ -10,6 +10,8 @@ class ErrorEvent(BaseModel): + """An error event from the application.""" + error: ErrorModel event: Literal["error"] diff --git a/src/kernel/types/shared/heartbeat_event.py b/src/kernel/types/shared/heartbeat_event.py index d5ca811..3745e9b 100644 --- a/src/kernel/types/shared/heartbeat_event.py +++ b/src/kernel/types/shared/heartbeat_event.py @@ -9,6 +9,8 @@ class HeartbeatEvent(BaseModel): + """Heartbeat event sent periodically to keep SSE connection alive.""" + event: Literal["sse_heartbeat"] """Event type identifier (always "sse_heartbeat").""" diff --git a/src/kernel/types/shared/log_event.py b/src/kernel/types/shared/log_event.py index 69dbc56..078b6ec 100644 --- a/src/kernel/types/shared/log_event.py +++ b/src/kernel/types/shared/log_event.py @@ -9,6 +9,8 @@ class LogEvent(BaseModel): + """A log entry from the application.""" + event: Literal["log"] """Event type identifier (always "log").""" diff --git a/src/kernel/types/shared_params/browser_extension.py b/src/kernel/types/shared_params/browser_extension.py index d81ac70..e6c2b8f 100644 --- a/src/kernel/types/shared_params/browser_extension.py +++ b/src/kernel/types/shared_params/browser_extension.py @@ -8,6 +8,11 @@ class BrowserExtension(TypedDict, total=False): + """Extension selection for the browser session. + + Provide either id or name of an extension uploaded to Kernel. + """ + id: str """Extension ID to load for this browser session""" diff --git a/src/kernel/types/shared_params/browser_profile.py b/src/kernel/types/shared_params/browser_profile.py index e1027d2..51187db 100644 --- a/src/kernel/types/shared_params/browser_profile.py +++ b/src/kernel/types/shared_params/browser_profile.py @@ -8,6 +8,12 @@ class BrowserProfile(TypedDict, total=False): + """Profile selection for the browser session. + + Provide either id or name. If specified, the + matching profile will be loaded into the browser session. Profiles must be created beforehand. + """ + id: str """Profile ID to load for this browser session""" diff --git a/src/kernel/types/shared_params/browser_viewport.py b/src/kernel/types/shared_params/browser_viewport.py index b7cb2f0..9236547 100644 --- a/src/kernel/types/shared_params/browser_viewport.py +++ b/src/kernel/types/shared_params/browser_viewport.py @@ -8,6 +8,15 @@ class BrowserViewport(TypedDict, total=False): + """Initial browser window size in pixels with optional refresh rate. + + If omitted, image defaults apply (1920x1080@25). + Only specific viewport configurations are supported. The server will reject unsupported combinations. + Supported resolutions are: 2560x1440@10, 1920x1080@25, 1920x1200@25, 1440x900@25, 1024x768@60, 1200x800@60 + If refresh_rate is not provided, it will be automatically determined from the width and height if they match a supported configuration exactly. + Note: Higher resolutions may affect the responsiveness of live view browser + """ + height: Required[int] """Browser window height in pixels.""" From 169539a9b861b90f554b192dc8e457ad17a851a0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 19:34:54 +0000 Subject: [PATCH 7/8] feat: Enhance AuthAgent model with last_auth_check_at field --- .stats.yml | 4 ++-- src/kernel/types/agents/auth_agent.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4bf353a..135345a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 82 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-b6957db438b01d979b62de21d4e674601b37d55b850b95a6e2b4c771aad5e840.yml -openapi_spec_hash: 1c8aac8322bc9df8f1b82a7e7a0c692b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-dac11bdb857e700a8c39d183e753ddd1ebaaca69fd9fc5ee57d6b56b70b00e6e.yml +openapi_spec_hash: 78fbc50dd0b61cdc87564fbea278ee23 config_hash: a4b4d14bdf6af723b235a6981977627c diff --git a/src/kernel/types/agents/auth_agent.py b/src/kernel/types/agents/auth_agent.py index ff9f5e9..423b92e 100644 --- a/src/kernel/types/agents/auth_agent.py +++ b/src/kernel/types/agents/auth_agent.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Optional +from datetime import datetime from typing_extensions import Literal from ..._models import BaseModel @@ -23,3 +25,6 @@ class AuthAgent(BaseModel): status: Literal["AUTHENTICATED", "NEEDS_AUTH"] """Current authentication status of the managed profile""" + + last_auth_check_at: Optional[datetime] = None + """When the last authentication check was performed""" From 08a2e176116beafee50e37a832c14df7ea4568e5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 19:35:14 +0000 Subject: [PATCH 8/8] release: 0.23.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 26 ++++++++++++++++++++++++++ pyproject.toml | 2 +- src/kernel/_version.py | 2 +- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index cb9d254..7f3f5c8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.22.0" + ".": "0.23.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b9f3aa1..f8e2118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## 0.23.0 (2025-12-11) + +Full Changelog: [v0.22.0...v0.23.0](https://github.com/onkernel/kernel-python-sdk/compare/v0.22.0...v0.23.0) + +### Features + +* [wip] Browser pools polish pass ([2ba9e57](https://github.com/onkernel/kernel-python-sdk/commit/2ba9e5740d0b12605de3b48af06658b8db47ba11)) +* enhance agent authentication API with new endpoints and request… ([84a794c](https://github.com/onkernel/kernel-python-sdk/commit/84a794c35f5a454b047d53b37867586f85e2536a)) +* Enhance agent authentication with optional login page URL and auth ch… ([d7bd8a2](https://github.com/onkernel/kernel-python-sdk/commit/d7bd8a22d40243c043e14b899dd2006fc0d51a19)) +* Enhance AuthAgent model with last_auth_check_at field ([169539a](https://github.com/onkernel/kernel-python-sdk/commit/169539a9b861b90f554b192dc8e457ad17a851a0)) + + +### Bug Fixes + +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([c8d571c](https://github.com/onkernel/kernel-python-sdk/commit/c8d571c785a4488549701aad525357a9fabec69f)) + + +### Chores + +* add missing docstrings ([75fa7cb](https://github.com/onkernel/kernel-python-sdk/commit/75fa7cb7b954f8a68cc5eb74ad3196b350e8f9dd)) + + +### Refactors + +* **browser:** remove persistence option UI ([57af2e1](https://github.com/onkernel/kernel-python-sdk/commit/57af2e181b716145ff3e11a0d74c04dd332f9e35)) + ## 0.22.0 (2025-12-05) Full Changelog: [v0.21.0...v0.22.0](https://github.com/onkernel/kernel-python-sdk/compare/v0.21.0...v0.22.0) diff --git a/pyproject.toml b/pyproject.toml index facf999..6e76a58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.22.0" +version = "0.23.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 c911504..6e51841 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.22.0" # x-release-please-version +__version__ = "0.23.0" # x-release-please-version