From f24a387432ff8e506c143da566e65d5674c7ff6c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 20:32:30 +0000 Subject: [PATCH 1/2] chore: add Managed Auth API planning doc --- .stats.yml | 8 +- api.md | 43 +- src/kernel/_client.py | 38 + src/kernel/resources/__init__.py | 14 + src/kernel/resources/agents/auth/auth.py | 186 ++-- .../resources/agents/auth/invocations.py | 227 +++-- src/kernel/resources/auth/__init__.py | 33 + src/kernel/resources/auth/auth.py | 102 ++ src/kernel/resources/auth/connections.py | 887 ++++++++++++++++++ src/kernel/resources/credential_providers.py | 121 ++- src/kernel/types/__init__.py | 4 + .../agents/agent_auth_invocation_response.py | 15 +- .../agents/auth/invocation_submit_params.py | 6 +- src/kernel/types/agents/auth_agent.py | 55 +- .../auth_agent_invocation_create_response.py | 9 +- src/kernel/types/agents/auth_create_params.py | 15 + src/kernel/types/agents/discovered_field.py | 6 + src/kernel/types/auth/__init__.py | 12 + .../types/auth/connection_create_params.py | 89 ++ .../types/auth/connection_follow_response.py | 109 +++ .../types/auth/connection_list_params.py | 21 + .../types/auth/connection_login_params.py | 12 + .../types/auth/connection_submit_params.py | 19 + src/kernel/types/auth/login_response.py | 31 + src/kernel/types/auth/managed_auth.py | 181 ++++ .../types/auth/submit_fields_response.py | 12 + src/kernel/types/credential_provider.py | 3 + .../credential_provider_create_params.py | 3 + src/kernel/types/credential_provider_item.py | 29 + ...credential_provider_list_items_response.py | 12 + .../credential_provider_update_params.py | 3 + .../agents/auth/test_invocations.py | 588 ++++++------ tests/api_resources/agents/test_auth.py | 330 ++++--- tests/api_resources/auth/__init__.py | 1 + tests/api_resources/auth/test_connections.py | 715 ++++++++++++++ .../test_credential_providers.py | 95 ++ 36 files changed, 3439 insertions(+), 595 deletions(-) create mode 100644 src/kernel/resources/auth/__init__.py create mode 100644 src/kernel/resources/auth/auth.py create mode 100644 src/kernel/resources/auth/connections.py create mode 100644 src/kernel/types/auth/__init__.py create mode 100644 src/kernel/types/auth/connection_create_params.py create mode 100644 src/kernel/types/auth/connection_follow_response.py create mode 100644 src/kernel/types/auth/connection_list_params.py create mode 100644 src/kernel/types/auth/connection_login_params.py create mode 100644 src/kernel/types/auth/connection_submit_params.py create mode 100644 src/kernel/types/auth/login_response.py create mode 100644 src/kernel/types/auth/managed_auth.py create mode 100644 src/kernel/types/auth/submit_fields_response.py create mode 100644 src/kernel/types/credential_provider_item.py create mode 100644 src/kernel/types/credential_provider_list_items_response.py create mode 100644 tests/api_resources/auth/__init__.py create mode 100644 tests/api_resources/auth/test_connections.py diff --git a/.stats.yml b/.stats.yml index 91fd3b68..29643219 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 100 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-a6d93dc291278035c96add38bb6150ec2b9ba8bbabb4676e3dbbb8444cf3b1e4.yml -openapi_spec_hash: 694bcc56d94fd0ff0d1f7b0fc1dae8ba -config_hash: 62e33cf2ed8fe0b4ceebba63367481ad +configured_endpoints: 108 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-3fbe762c99e8a120c426ac22bc1fa257c9127d631b12a38a6440a37f52935543.yml +openapi_spec_hash: 5a190df210ed90b20a71c5061ff43917 +config_hash: 38c9b3b355025daf9bb643040e4af94e diff --git a/api.md b/api.md index d55fecff..cb50de40 100644 --- a/api.md +++ b/api.md @@ -234,6 +234,34 @@ Methods: - client.profiles.delete(id_or_name) -> None - client.profiles.download(id_or_name) -> BinaryAPIResponse +# Auth + +## Connections + +Types: + +```python +from kernel.types.auth import ( + LoginRequest, + LoginResponse, + ManagedAuth, + ManagedAuthCreateRequest, + SubmitFieldsRequest, + SubmitFieldsResponse, + ConnectionFollowResponse, +) +``` + +Methods: + +- client.auth.connections.create(\*\*params) -> ManagedAuth +- client.auth.connections.retrieve(id) -> ManagedAuth +- client.auth.connections.list(\*\*params) -> SyncOffsetPagination[ManagedAuth] +- client.auth.connections.delete(id) -> None +- client.auth.connections.follow(id) -> ConnectionFollowResponse +- client.auth.connections.login(id, \*\*params) -> LoginResponse +- client.auth.connections.submit(id, \*\*params) -> SubmitFieldsResponse + # Proxies Types: @@ -360,17 +388,20 @@ Types: from kernel.types import ( CreateCredentialProviderRequest, CredentialProvider, + CredentialProviderItem, CredentialProviderTestResult, UpdateCredentialProviderRequest, CredentialProviderListResponse, + CredentialProviderListItemsResponse, ) ``` Methods: -- client.credential_providers.create(\*\*params) -> CredentialProvider -- client.credential_providers.retrieve(id) -> CredentialProvider -- client.credential_providers.update(id, \*\*params) -> CredentialProvider -- client.credential_providers.list() -> CredentialProviderListResponse -- client.credential_providers.delete(id) -> None -- client.credential_providers.test(id) -> CredentialProviderTestResult +- client.credential_providers.create(\*\*params) -> CredentialProvider +- client.credential_providers.retrieve(id) -> CredentialProvider +- client.credential_providers.update(id, \*\*params) -> CredentialProvider +- client.credential_providers.list() -> CredentialProviderListResponse +- client.credential_providers.delete(id) -> None +- client.credential_providers.list_items(id) -> CredentialProviderListItemsResponse +- client.credential_providers.test(id) -> CredentialProviderTestResult diff --git a/src/kernel/_client.py b/src/kernel/_client.py index daf57998..07c3b682 100644 --- a/src/kernel/_client.py +++ b/src/kernel/_client.py @@ -33,6 +33,7 @@ if TYPE_CHECKING: from .resources import ( apps, + auth, agents, proxies, browsers, @@ -47,6 +48,7 @@ from .resources.apps import AppsResource, AsyncAppsResource from .resources.proxies import ProxiesResource, AsyncProxiesResource from .resources.profiles import ProfilesResource, AsyncProfilesResource + from .resources.auth.auth import AuthResource, AsyncAuthResource from .resources.extensions import ExtensionsResource, AsyncExtensionsResource from .resources.credentials import CredentialsResource, AsyncCredentialsResource from .resources.deployments import DeploymentsResource, AsyncDeploymentsResource @@ -183,6 +185,12 @@ def profiles(self) -> ProfilesResource: return ProfilesResource(self) + @cached_property + def auth(self) -> AuthResource: + from .resources.auth import AuthResource + + return AuthResource(self) + @cached_property def proxies(self) -> ProxiesResource: from .resources.proxies import ProxiesResource @@ -443,6 +451,12 @@ def profiles(self) -> AsyncProfilesResource: return AsyncProfilesResource(self) + @cached_property + def auth(self) -> AsyncAuthResource: + from .resources.auth import AsyncAuthResource + + return AsyncAuthResource(self) + @cached_property def proxies(self) -> AsyncProxiesResource: from .resources.proxies import AsyncProxiesResource @@ -630,6 +644,12 @@ def profiles(self) -> profiles.ProfilesResourceWithRawResponse: return ProfilesResourceWithRawResponse(self._client.profiles) + @cached_property + def auth(self) -> auth.AuthResourceWithRawResponse: + from .resources.auth import AuthResourceWithRawResponse + + return AuthResourceWithRawResponse(self._client.auth) + @cached_property def proxies(self) -> proxies.ProxiesResourceWithRawResponse: from .resources.proxies import ProxiesResourceWithRawResponse @@ -703,6 +723,12 @@ def profiles(self) -> profiles.AsyncProfilesResourceWithRawResponse: return AsyncProfilesResourceWithRawResponse(self._client.profiles) + @cached_property + def auth(self) -> auth.AsyncAuthResourceWithRawResponse: + from .resources.auth import AsyncAuthResourceWithRawResponse + + return AsyncAuthResourceWithRawResponse(self._client.auth) + @cached_property def proxies(self) -> proxies.AsyncProxiesResourceWithRawResponse: from .resources.proxies import AsyncProxiesResourceWithRawResponse @@ -776,6 +802,12 @@ def profiles(self) -> profiles.ProfilesResourceWithStreamingResponse: return ProfilesResourceWithStreamingResponse(self._client.profiles) + @cached_property + def auth(self) -> auth.AuthResourceWithStreamingResponse: + from .resources.auth import AuthResourceWithStreamingResponse + + return AuthResourceWithStreamingResponse(self._client.auth) + @cached_property def proxies(self) -> proxies.ProxiesResourceWithStreamingResponse: from .resources.proxies import ProxiesResourceWithStreamingResponse @@ -849,6 +881,12 @@ def profiles(self) -> profiles.AsyncProfilesResourceWithStreamingResponse: return AsyncProfilesResourceWithStreamingResponse(self._client.profiles) + @cached_property + def auth(self) -> auth.AsyncAuthResourceWithStreamingResponse: + from .resources.auth import AsyncAuthResourceWithStreamingResponse + + return AsyncAuthResourceWithStreamingResponse(self._client.auth) + @cached_property def proxies(self) -> proxies.AsyncProxiesResourceWithStreamingResponse: from .resources.proxies import AsyncProxiesResourceWithStreamingResponse diff --git a/src/kernel/resources/__init__.py b/src/kernel/resources/__init__.py index 50db63bb..31a325b2 100644 --- a/src/kernel/resources/__init__.py +++ b/src/kernel/resources/__init__.py @@ -8,6 +8,14 @@ AppsResourceWithStreamingResponse, AsyncAppsResourceWithStreamingResponse, ) +from .auth import ( + AuthResource, + AsyncAuthResource, + AuthResourceWithRawResponse, + AsyncAuthResourceWithRawResponse, + AuthResourceWithStreamingResponse, + AsyncAuthResourceWithStreamingResponse, +) from .agents import ( AgentsResource, AsyncAgentsResource, @@ -120,6 +128,12 @@ "AsyncProfilesResourceWithRawResponse", "ProfilesResourceWithStreamingResponse", "AsyncProfilesResourceWithStreamingResponse", + "AuthResource", + "AsyncAuthResource", + "AuthResourceWithRawResponse", + "AsyncAuthResourceWithRawResponse", + "AuthResourceWithStreamingResponse", + "AsyncAuthResourceWithStreamingResponse", "ProxiesResource", "AsyncProxiesResource", "ProxiesResourceWithRawResponse", diff --git a/src/kernel/resources/agents/auth/auth.py b/src/kernel/resources/agents/auth/auth.py index 4a541f73..05e83c50 100644 --- a/src/kernel/resources/agents/auth/auth.py +++ b/src/kernel/resources/agents/auth/auth.py @@ -2,6 +2,8 @@ from __future__ import annotations +import typing_extensions + import httpx from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given @@ -54,6 +56,7 @@ def with_streaming_response(self) -> AuthResourceWithStreamingResponse: """ return AuthResourceWithStreamingResponse(self) + @typing_extensions.deprecated("deprecated") def create( self, *, @@ -71,10 +74,11 @@ def create( 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. + **Deprecated: Use POST /auth/connections instead.** 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: domain: Domain for authentication @@ -85,6 +89,21 @@ def create( (besides the primary domain). Useful when login pages redirect to different domains. + The following SSO/OAuth provider domains are automatically allowed by default + and do not need to be specified: + + - Google: accounts.google.com + - Microsoft/Azure AD: login.microsoftonline.com, login.live.com + - Okta: _.okta.com, _.oktapreview.com + - Auth0: _.auth0.com, _.us.auth0.com, _.eu.auth0.com, _.au.auth0.com + - Apple: appleid.apple.com + - GitHub: github.com + - Facebook/Meta: www.facebook.com + - LinkedIn: www.linkedin.com + - Amazon Cognito: \\**.amazoncognito.com + - OneLogin: \\**.onelogin.com + - Ping Identity: _.pingone.com, _.pingidentity.com + credential_name: Optional name of an existing credential to use for this auth agent. If provided, the credential will be linked to the agent and its values will be used to auto-fill the login form on invocation. @@ -121,6 +140,7 @@ def create( cast_to=AuthAgent, ) + @typing_extensions.deprecated("deprecated") def retrieve( self, id: str, @@ -132,10 +152,9 @@ def retrieve( 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. + """ + **Deprecated: Use GET /auth/connections/{id} instead.** Retrieve an auth agent + by its ID. Returns the current authentication status of the managed profile. Args: extra_headers: Send extra headers @@ -156,6 +175,7 @@ def retrieve( cast_to=AuthAgent, ) + @typing_extensions.deprecated("deprecated") def list( self, *, @@ -171,7 +191,8 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncOffsetPagination[AuthAgent]: """ - List auth agents with optional filters for profile_name and domain. + **Deprecated: Use GET /auth/connections instead.** List auth agents with + optional filters for profile_name and domain. Args: domain: Filter by domain @@ -211,6 +232,7 @@ def list( model=AuthAgent, ) + @typing_extensions.deprecated("deprecated") def delete( self, id: str, @@ -222,9 +244,9 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: - """Deletes an auth agent and terminates its workflow. - - This will: + """ + **Deprecated: Use DELETE /auth/connections/{id} instead.** Deletes an auth agent + and terminates its workflow. This will: - Soft delete the auth agent record - Gracefully terminate the agent's Temporal workflow @@ -275,6 +297,7 @@ def with_streaming_response(self) -> AsyncAuthResourceWithStreamingResponse: """ return AsyncAuthResourceWithStreamingResponse(self) + @typing_extensions.deprecated("deprecated") async def create( self, *, @@ -292,10 +315,11 @@ async def create( 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. + **Deprecated: Use POST /auth/connections instead.** 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: domain: Domain for authentication @@ -306,6 +330,21 @@ async def create( (besides the primary domain). Useful when login pages redirect to different domains. + The following SSO/OAuth provider domains are automatically allowed by default + and do not need to be specified: + + - Google: accounts.google.com + - Microsoft/Azure AD: login.microsoftonline.com, login.live.com + - Okta: _.okta.com, _.oktapreview.com + - Auth0: _.auth0.com, _.us.auth0.com, _.eu.auth0.com, _.au.auth0.com + - Apple: appleid.apple.com + - GitHub: github.com + - Facebook/Meta: www.facebook.com + - LinkedIn: www.linkedin.com + - Amazon Cognito: \\**.amazoncognito.com + - OneLogin: \\**.onelogin.com + - Ping Identity: _.pingone.com, _.pingidentity.com + credential_name: Optional name of an existing credential to use for this auth agent. If provided, the credential will be linked to the agent and its values will be used to auto-fill the login form on invocation. @@ -342,6 +381,7 @@ async def create( cast_to=AuthAgent, ) + @typing_extensions.deprecated("deprecated") async def retrieve( self, id: str, @@ -353,10 +393,9 @@ async def retrieve( 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. + """ + **Deprecated: Use GET /auth/connections/{id} instead.** Retrieve an auth agent + by its ID. Returns the current authentication status of the managed profile. Args: extra_headers: Send extra headers @@ -377,6 +416,7 @@ async def retrieve( cast_to=AuthAgent, ) + @typing_extensions.deprecated("deprecated") def list( self, *, @@ -392,7 +432,8 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[AuthAgent, AsyncOffsetPagination[AuthAgent]]: """ - List auth agents with optional filters for profile_name and domain. + **Deprecated: Use GET /auth/connections instead.** List auth agents with + optional filters for profile_name and domain. Args: domain: Filter by domain @@ -432,6 +473,7 @@ def list( model=AuthAgent, ) + @typing_extensions.deprecated("deprecated") async def delete( self, id: str, @@ -443,9 +485,9 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: - """Deletes an auth agent and terminates its workflow. - - This will: + """ + **Deprecated: Use DELETE /auth/connections/{id} instead.** Deletes an auth agent + and terminates its workflow. This will: - Soft delete the auth agent record - Gracefully terminate the agent's Temporal workflow @@ -476,17 +518,25 @@ class AuthResourceWithRawResponse: def __init__(self, auth: AuthResource) -> None: self._auth = auth - self.create = to_raw_response_wrapper( - auth.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + auth.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = to_raw_response_wrapper( - auth.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + auth.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = to_raw_response_wrapper( - auth.list, + self.list = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + auth.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = to_raw_response_wrapper( - auth.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + auth.delete, # pyright: ignore[reportDeprecated], + ) ) @cached_property @@ -498,17 +548,25 @@ class AsyncAuthResourceWithRawResponse: def __init__(self, auth: AsyncAuthResource) -> None: self._auth = auth - self.create = async_to_raw_response_wrapper( - auth.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + auth.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = async_to_raw_response_wrapper( - auth.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + auth.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = async_to_raw_response_wrapper( - auth.list, + self.list = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + auth.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = async_to_raw_response_wrapper( - auth.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + auth.delete, # pyright: ignore[reportDeprecated], + ) ) @cached_property @@ -520,17 +578,25 @@ class AuthResourceWithStreamingResponse: def __init__(self, auth: AuthResource) -> None: self._auth = auth - self.create = to_streamed_response_wrapper( - auth.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + auth.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = to_streamed_response_wrapper( - auth.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + auth.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = to_streamed_response_wrapper( - auth.list, + self.list = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + auth.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = to_streamed_response_wrapper( - auth.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + auth.delete, # pyright: ignore[reportDeprecated], + ) ) @cached_property @@ -542,17 +608,25 @@ class AsyncAuthResourceWithStreamingResponse: def __init__(self, auth: AsyncAuthResource) -> None: self._auth = auth - self.create = async_to_streamed_response_wrapper( - auth.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + auth.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = async_to_streamed_response_wrapper( - auth.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + auth.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.list = async_to_streamed_response_wrapper( - auth.list, + self.list = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + auth.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = async_to_streamed_response_wrapper( - auth.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + auth.delete, # pyright: ignore[reportDeprecated], + ) ) @cached_property diff --git a/src/kernel/resources/agents/auth/invocations.py b/src/kernel/resources/agents/auth/invocations.py index fcd5e3a6..617afddd 100644 --- a/src/kernel/resources/agents/auth/invocations.py +++ b/src/kernel/resources/agents/auth/invocations.py @@ -2,6 +2,7 @@ from __future__ import annotations +import typing_extensions from typing import Dict from typing_extensions import Literal, overload @@ -47,6 +48,7 @@ def with_streaming_response(self) -> InvocationsResourceWithStreamingResponse: """ return InvocationsResourceWithStreamingResponse(self) + @typing_extensions.deprecated("deprecated") def create( self, *, @@ -59,11 +61,10 @@ def create( 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. + """ + **Deprecated: Use POST /auth/connections/{id}/login instead.** 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 @@ -95,6 +96,7 @@ def create( cast_to=AuthAgentInvocationCreateResponse, ) + @typing_extensions.deprecated("deprecated") def retrieve( self, invocation_id: str, @@ -106,10 +108,10 @@ def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthInvocationResponse: - """Returns invocation details including status, app_name, and domain. - - Supports both - API key and JWT (from exchange endpoint) authentication. + """ + **Deprecated: Use GET /auth/connections/{id} instead.** Returns invocation + details including status, app_name, and domain. Supports both API key and JWT + (from exchange endpoint) authentication. Args: extra_headers: Send extra headers @@ -130,6 +132,7 @@ def retrieve( cast_to=AgentAuthInvocationResponse, ) + @typing_extensions.deprecated("deprecated") def exchange( self, invocation_id: str, @@ -142,10 +145,10 @@ def exchange( 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). + """ + **Deprecated: Use POST /auth/connections/{id}/exchange instead.** 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 @@ -169,6 +172,7 @@ def exchange( cast_to=InvocationExchangeResponse, ) + @typing_extensions.deprecated("deprecated") @overload def submit( self, @@ -182,11 +186,10 @@ def submit( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthSubmitResponse: - """Submits field values for the discovered login form. - - Returns immediately after - submission is accepted. Poll the invocation endpoint to track progress and get - results. + """ + **Deprecated: Use POST /auth/connections/{id}/submit instead.** Submits field + values for the discovered login form. Returns immediately after submission is + accepted. Poll the invocation endpoint to track progress and get results. Args: field_values: Values for the discovered login fields @@ -201,6 +204,7 @@ def submit( """ ... + @typing_extensions.deprecated("deprecated") @overload def submit( self, @@ -214,11 +218,10 @@ def submit( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthSubmitResponse: - """Submits field values for the discovered login form. - - Returns immediately after - submission is accepted. Poll the invocation endpoint to track progress and get - results. + """ + **Deprecated: Use POST /auth/connections/{id}/submit instead.** Submits field + values for the discovered login form. Returns immediately after submission is + accepted. Poll the invocation endpoint to track progress and get results. Args: sso_button: Selector of SSO button to click @@ -233,12 +236,13 @@ def submit( """ ... + @typing_extensions.deprecated("deprecated") @overload def submit( self, invocation_id: str, *, - selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "security_key"], + selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "password"], # 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, @@ -246,14 +250,13 @@ def submit( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthSubmitResponse: - """Submits field values for the discovered login form. - - Returns immediately after - submission is accepted. Poll the invocation endpoint to track progress and get - results. + """ + **Deprecated: Use POST /auth/connections/{id}/submit instead.** Submits field + values for the discovered login form. Returns immediately after submission is + accepted. Poll the invocation endpoint to track progress and get results. Args: - selected_mfa_type: The MFA delivery method type + selected_mfa_type: The MFA delivery method type (includes password for auth method selection pages) extra_headers: Send extra headers @@ -265,6 +268,7 @@ def submit( """ ... + @typing_extensions.deprecated("deprecated") @required_args(["field_values"], ["sso_button"], ["selected_mfa_type"]) def submit( self, @@ -272,7 +276,7 @@ def submit( *, field_values: Dict[str, str] | Omit = omit, sso_button: str | Omit = omit, - selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "security_key"] | Omit = omit, + selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "password"] | 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, @@ -319,6 +323,7 @@ def with_streaming_response(self) -> AsyncInvocationsResourceWithStreamingRespon """ return AsyncInvocationsResourceWithStreamingResponse(self) + @typing_extensions.deprecated("deprecated") async def create( self, *, @@ -331,11 +336,10 @@ async def create( 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. + """ + **Deprecated: Use POST /auth/connections/{id}/login instead.** 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 @@ -367,6 +371,7 @@ async def create( cast_to=AuthAgentInvocationCreateResponse, ) + @typing_extensions.deprecated("deprecated") async def retrieve( self, invocation_id: str, @@ -378,10 +383,10 @@ async def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthInvocationResponse: - """Returns invocation details including status, app_name, and domain. - - Supports both - API key and JWT (from exchange endpoint) authentication. + """ + **Deprecated: Use GET /auth/connections/{id} instead.** Returns invocation + details including status, app_name, and domain. Supports both API key and JWT + (from exchange endpoint) authentication. Args: extra_headers: Send extra headers @@ -402,6 +407,7 @@ async def retrieve( cast_to=AgentAuthInvocationResponse, ) + @typing_extensions.deprecated("deprecated") async def exchange( self, invocation_id: str, @@ -414,10 +420,10 @@ async def exchange( 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). + """ + **Deprecated: Use POST /auth/connections/{id}/exchange instead.** 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 @@ -441,6 +447,7 @@ async def exchange( cast_to=InvocationExchangeResponse, ) + @typing_extensions.deprecated("deprecated") @overload async def submit( self, @@ -454,11 +461,10 @@ async def submit( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthSubmitResponse: - """Submits field values for the discovered login form. - - Returns immediately after - submission is accepted. Poll the invocation endpoint to track progress and get - results. + """ + **Deprecated: Use POST /auth/connections/{id}/submit instead.** Submits field + values for the discovered login form. Returns immediately after submission is + accepted. Poll the invocation endpoint to track progress and get results. Args: field_values: Values for the discovered login fields @@ -473,6 +479,7 @@ async def submit( """ ... + @typing_extensions.deprecated("deprecated") @overload async def submit( self, @@ -486,11 +493,10 @@ async def submit( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthSubmitResponse: - """Submits field values for the discovered login form. - - Returns immediately after - submission is accepted. Poll the invocation endpoint to track progress and get - results. + """ + **Deprecated: Use POST /auth/connections/{id}/submit instead.** Submits field + values for the discovered login form. Returns immediately after submission is + accepted. Poll the invocation endpoint to track progress and get results. Args: sso_button: Selector of SSO button to click @@ -505,12 +511,13 @@ async def submit( """ ... + @typing_extensions.deprecated("deprecated") @overload async def submit( self, invocation_id: str, *, - selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "security_key"], + selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "password"], # 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, @@ -518,14 +525,13 @@ async def submit( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthSubmitResponse: - """Submits field values for the discovered login form. - - Returns immediately after - submission is accepted. Poll the invocation endpoint to track progress and get - results. + """ + **Deprecated: Use POST /auth/connections/{id}/submit instead.** Submits field + values for the discovered login form. Returns immediately after submission is + accepted. Poll the invocation endpoint to track progress and get results. Args: - selected_mfa_type: The MFA delivery method type + selected_mfa_type: The MFA delivery method type (includes password for auth method selection pages) extra_headers: Send extra headers @@ -537,6 +543,7 @@ async def submit( """ ... + @typing_extensions.deprecated("deprecated") @required_args(["field_values"], ["sso_button"], ["selected_mfa_type"]) async def submit( self, @@ -544,7 +551,7 @@ async def submit( *, field_values: Dict[str, str] | Omit = omit, sso_button: str | Omit = omit, - selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "security_key"] | Omit = omit, + selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "password"] | 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, @@ -575,17 +582,25 @@ class InvocationsResourceWithRawResponse: def __init__(self, invocations: InvocationsResource) -> None: self._invocations = invocations - self.create = to_raw_response_wrapper( - invocations.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + invocations.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = to_raw_response_wrapper( - invocations.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + invocations.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.exchange = to_raw_response_wrapper( - invocations.exchange, + self.exchange = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + invocations.exchange, # pyright: ignore[reportDeprecated], + ) ) - self.submit = to_raw_response_wrapper( - invocations.submit, + self.submit = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + invocations.submit, # pyright: ignore[reportDeprecated], + ) ) @@ -593,17 +608,25 @@ class AsyncInvocationsResourceWithRawResponse: def __init__(self, invocations: AsyncInvocationsResource) -> None: self._invocations = invocations - self.create = async_to_raw_response_wrapper( - invocations.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + invocations.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = async_to_raw_response_wrapper( - invocations.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + invocations.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.exchange = async_to_raw_response_wrapper( - invocations.exchange, + self.exchange = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + invocations.exchange, # pyright: ignore[reportDeprecated], + ) ) - self.submit = async_to_raw_response_wrapper( - invocations.submit, + self.submit = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + invocations.submit, # pyright: ignore[reportDeprecated], + ) ) @@ -611,17 +634,25 @@ class InvocationsResourceWithStreamingResponse: def __init__(self, invocations: InvocationsResource) -> None: self._invocations = invocations - self.create = to_streamed_response_wrapper( - invocations.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + invocations.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = to_streamed_response_wrapper( - invocations.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + invocations.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.exchange = to_streamed_response_wrapper( - invocations.exchange, + self.exchange = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + invocations.exchange, # pyright: ignore[reportDeprecated], + ) ) - self.submit = to_streamed_response_wrapper( - invocations.submit, + self.submit = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + invocations.submit, # pyright: ignore[reportDeprecated], + ) ) @@ -629,15 +660,23 @@ class AsyncInvocationsResourceWithStreamingResponse: def __init__(self, invocations: AsyncInvocationsResource) -> None: self._invocations = invocations - self.create = async_to_streamed_response_wrapper( - invocations.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + invocations.create, # pyright: ignore[reportDeprecated], + ) ) - self.retrieve = async_to_streamed_response_wrapper( - invocations.retrieve, + self.retrieve = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + invocations.retrieve, # pyright: ignore[reportDeprecated], + ) ) - self.exchange = async_to_streamed_response_wrapper( - invocations.exchange, + self.exchange = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + invocations.exchange, # pyright: ignore[reportDeprecated], + ) ) - self.submit = async_to_streamed_response_wrapper( - invocations.submit, + self.submit = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + invocations.submit, # pyright: ignore[reportDeprecated], + ) ) diff --git a/src/kernel/resources/auth/__init__.py b/src/kernel/resources/auth/__init__.py new file mode 100644 index 00000000..a863677f --- /dev/null +++ b/src/kernel/resources/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 .connections import ( + ConnectionsResource, + AsyncConnectionsResource, + ConnectionsResourceWithRawResponse, + AsyncConnectionsResourceWithRawResponse, + ConnectionsResourceWithStreamingResponse, + AsyncConnectionsResourceWithStreamingResponse, +) + +__all__ = [ + "ConnectionsResource", + "AsyncConnectionsResource", + "ConnectionsResourceWithRawResponse", + "AsyncConnectionsResourceWithRawResponse", + "ConnectionsResourceWithStreamingResponse", + "AsyncConnectionsResourceWithStreamingResponse", + "AuthResource", + "AsyncAuthResource", + "AuthResourceWithRawResponse", + "AsyncAuthResourceWithRawResponse", + "AuthResourceWithStreamingResponse", + "AsyncAuthResourceWithStreamingResponse", +] diff --git a/src/kernel/resources/auth/auth.py b/src/kernel/resources/auth/auth.py new file mode 100644 index 00000000..b5980e6b --- /dev/null +++ b/src/kernel/resources/auth/auth.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 ..._resource import SyncAPIResource, AsyncAPIResource +from .connections import ( + ConnectionsResource, + AsyncConnectionsResource, + ConnectionsResourceWithRawResponse, + AsyncConnectionsResourceWithRawResponse, + ConnectionsResourceWithStreamingResponse, + AsyncConnectionsResourceWithStreamingResponse, +) + +__all__ = ["AuthResource", "AsyncAuthResource"] + + +class AuthResource(SyncAPIResource): + @cached_property + def connections(self) -> ConnectionsResource: + return ConnectionsResource(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/kernel/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/kernel/kernel-python-sdk#with_streaming_response + """ + return AuthResourceWithStreamingResponse(self) + + +class AsyncAuthResource(AsyncAPIResource): + @cached_property + def connections(self) -> AsyncConnectionsResource: + return AsyncConnectionsResource(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/kernel/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/kernel/kernel-python-sdk#with_streaming_response + """ + return AsyncAuthResourceWithStreamingResponse(self) + + +class AuthResourceWithRawResponse: + def __init__(self, auth: AuthResource) -> None: + self._auth = auth + + @cached_property + def connections(self) -> ConnectionsResourceWithRawResponse: + return ConnectionsResourceWithRawResponse(self._auth.connections) + + +class AsyncAuthResourceWithRawResponse: + def __init__(self, auth: AsyncAuthResource) -> None: + self._auth = auth + + @cached_property + def connections(self) -> AsyncConnectionsResourceWithRawResponse: + return AsyncConnectionsResourceWithRawResponse(self._auth.connections) + + +class AuthResourceWithStreamingResponse: + def __init__(self, auth: AuthResource) -> None: + self._auth = auth + + @cached_property + def connections(self) -> ConnectionsResourceWithStreamingResponse: + return ConnectionsResourceWithStreamingResponse(self._auth.connections) + + +class AsyncAuthResourceWithStreamingResponse: + def __init__(self, auth: AsyncAuthResource) -> None: + self._auth = auth + + @cached_property + def connections(self) -> AsyncConnectionsResourceWithStreamingResponse: + return AsyncConnectionsResourceWithStreamingResponse(self._auth.connections) diff --git a/src/kernel/resources/auth/connections.py b/src/kernel/resources/auth/connections.py new file mode 100644 index 00000000..0ae31538 --- /dev/null +++ b/src/kernel/resources/auth/connections.py @@ -0,0 +1,887 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Any, Dict, cast + +import httpx + +from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, 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 ..._streaming import Stream, AsyncStream +from ...pagination import SyncOffsetPagination, AsyncOffsetPagination +from ...types.auth import ( + connection_list_params, + connection_login_params, + connection_create_params, + connection_submit_params, +) +from ..._base_client import AsyncPaginator, make_request_options +from ...types.auth.managed_auth import ManagedAuth +from ...types.auth.login_response import LoginResponse +from ...types.auth.submit_fields_response import SubmitFieldsResponse +from ...types.auth.connection_follow_response import ConnectionFollowResponse + +__all__ = ["ConnectionsResource", "AsyncConnectionsResource"] + + +class ConnectionsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ConnectionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/kernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return ConnectionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ConnectionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/kernel/kernel-python-sdk#with_streaming_response + """ + return ConnectionsResourceWithStreamingResponse(self) + + def create( + self, + *, + domain: str, + profile_name: str, + allowed_domains: SequenceNotStr[str] | Omit = omit, + credential: connection_create_params.Credential | Omit = omit, + health_check_interval: int | Omit = omit, + login_url: str | Omit = omit, + proxy: connection_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, + ) -> ManagedAuth: + """Creates managed authentication for a profile and domain combination. + + Returns 409 + Conflict if managed auth already exists for the given profile and domain. + + Args: + domain: Domain for authentication + + profile_name: Name of the profile to manage authentication for + + allowed_domains: Additional domains valid for this auth flow (besides the primary domain). Useful + when login pages redirect to different domains. + + The following SSO/OAuth provider domains are automatically allowed by default + and do not need to be specified: + + - Google: accounts.google.com + - Microsoft/Azure AD: login.microsoftonline.com, login.live.com + - Okta: _.okta.com, _.oktapreview.com + - Auth0: _.auth0.com, _.us.auth0.com, _.eu.auth0.com, _.au.auth0.com + - Apple: appleid.apple.com + - GitHub: github.com + - Facebook/Meta: www.facebook.com + - LinkedIn: www.linkedin.com + - Amazon Cognito: \\**.amazoncognito.com + - OneLogin: \\**.onelogin.com + - Ping Identity: _.pingone.com, _.pingidentity.com + + credential: + Reference to credentials for managed auth. Use one of: + + - { name } for Kernel credentials + - { provider, path } for external provider item + - { provider, auto: true } for external provider domain lookup + + health_check_interval: Interval in seconds between automatic health checks. When set, the system + periodically verifies the authentication status and triggers re-authentication + if needed. Must be between 300 (5 minutes) and 86400 (24 hours). Default is 3600 + (1 hour). + + login_url: Optional login page URL to skip discovery + + 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( + "/auth/connections", + body=maybe_transform( + { + "domain": domain, + "profile_name": profile_name, + "allowed_domains": allowed_domains, + "credential": credential, + "health_check_interval": health_check_interval, + "login_url": login_url, + "proxy": proxy, + }, + connection_create_params.ConnectionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ManagedAuth, + ) + + 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, + ) -> ManagedAuth: + """Retrieve managed auth by its ID. + + Includes current flow state if a login is in + progress. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + f"/auth/connections/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ManagedAuth, + ) + + def list( + self, + *, + domain: str | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + profile_name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncOffsetPagination[ManagedAuth]: + """ + List managed auths with optional filters for profile_name and domain. + + Args: + domain: Filter by domain + + limit: Maximum number of results to return + + offset: Number of results to skip + + profile_name: Filter by profile name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/auth/connections", + page=SyncOffsetPagination[ManagedAuth], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "domain": domain, + "limit": limit, + "offset": offset, + "profile_name": profile_name, + }, + connection_list_params.ConnectionListParams, + ), + ), + model=ManagedAuth, + ) + + def delete( + 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, + ) -> None: + """Deletes managed auth and terminates its workflow. + + This will: + + - Delete the managed auth record + - Terminate the Temporal workflow + - Cancel any in-progress login flows + + 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}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/auth/connections/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def follow( + 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, + ) -> Stream[ConnectionFollowResponse]: + """ + Establishes a Server-Sent Events (SSE) stream that delivers real-time login flow + state updates. The stream terminates automatically once the flow reaches a + terminal state (SUCCESS, FAILED, EXPIRED, CANCELED). + + 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}") + extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})} + return self._get( + f"/auth/connections/{id}/events", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=cast( + Any, ConnectionFollowResponse + ), # Union types cannot be passed in as arguments in the type system + stream=True, + stream_cls=Stream[ConnectionFollowResponse], + ) + + def login( + self, + id: str, + *, + save_credential_as: 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, + ) -> LoginResponse: + """Starts a login flow for the managed auth. + + Returns immediately with a hosted URL + for the user to complete authentication, or triggers automatic re-auth if + credentials are stored. + + Args: + save_credential_as: If provided, saves credentials under this name upon successful login + + 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._post( + f"/auth/connections/{id}/login", + body=maybe_transform( + {"save_credential_as": save_credential_as}, connection_login_params.ConnectionLoginParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LoginResponse, + ) + + def submit( + self, + id: str, + *, + fields: Dict[str, str], + mfa_option_id: str | Omit = omit, + sso_button_selector: 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, + ) -> SubmitFieldsResponse: + """Submits field values for the login form. + + Poll the managed auth to track progress + and get results. + + Args: + fields: Map of field name to value + + mfa_option_id: Optional MFA option ID if user selected an MFA method + + sso_button_selector: Optional XPath selector if user chose to click an SSO button instead + + 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._post( + f"/auth/connections/{id}/submit", + body=maybe_transform( + { + "fields": fields, + "mfa_option_id": mfa_option_id, + "sso_button_selector": sso_button_selector, + }, + connection_submit_params.ConnectionSubmitParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SubmitFieldsResponse, + ) + + +class AsyncConnectionsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncConnectionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/kernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncConnectionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncConnectionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/kernel/kernel-python-sdk#with_streaming_response + """ + return AsyncConnectionsResourceWithStreamingResponse(self) + + async def create( + self, + *, + domain: str, + profile_name: str, + allowed_domains: SequenceNotStr[str] | Omit = omit, + credential: connection_create_params.Credential | Omit = omit, + health_check_interval: int | Omit = omit, + login_url: str | Omit = omit, + proxy: connection_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, + ) -> ManagedAuth: + """Creates managed authentication for a profile and domain combination. + + Returns 409 + Conflict if managed auth already exists for the given profile and domain. + + Args: + domain: Domain for authentication + + profile_name: Name of the profile to manage authentication for + + allowed_domains: Additional domains valid for this auth flow (besides the primary domain). Useful + when login pages redirect to different domains. + + The following SSO/OAuth provider domains are automatically allowed by default + and do not need to be specified: + + - Google: accounts.google.com + - Microsoft/Azure AD: login.microsoftonline.com, login.live.com + - Okta: _.okta.com, _.oktapreview.com + - Auth0: _.auth0.com, _.us.auth0.com, _.eu.auth0.com, _.au.auth0.com + - Apple: appleid.apple.com + - GitHub: github.com + - Facebook/Meta: www.facebook.com + - LinkedIn: www.linkedin.com + - Amazon Cognito: \\**.amazoncognito.com + - OneLogin: \\**.onelogin.com + - Ping Identity: _.pingone.com, _.pingidentity.com + + credential: + Reference to credentials for managed auth. Use one of: + + - { name } for Kernel credentials + - { provider, path } for external provider item + - { provider, auto: true } for external provider domain lookup + + health_check_interval: Interval in seconds between automatic health checks. When set, the system + periodically verifies the authentication status and triggers re-authentication + if needed. Must be between 300 (5 minutes) and 86400 (24 hours). Default is 3600 + (1 hour). + + login_url: Optional login page URL to skip discovery + + 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( + "/auth/connections", + body=await async_maybe_transform( + { + "domain": domain, + "profile_name": profile_name, + "allowed_domains": allowed_domains, + "credential": credential, + "health_check_interval": health_check_interval, + "login_url": login_url, + "proxy": proxy, + }, + connection_create_params.ConnectionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ManagedAuth, + ) + + 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, + ) -> ManagedAuth: + """Retrieve managed auth by its ID. + + Includes current flow state if a login is in + progress. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + f"/auth/connections/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ManagedAuth, + ) + + def list( + self, + *, + domain: str | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, + profile_name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ManagedAuth, AsyncOffsetPagination[ManagedAuth]]: + """ + List managed auths with optional filters for profile_name and domain. + + Args: + domain: Filter by domain + + limit: Maximum number of results to return + + offset: Number of results to skip + + profile_name: Filter by profile name + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/auth/connections", + page=AsyncOffsetPagination[ManagedAuth], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "domain": domain, + "limit": limit, + "offset": offset, + "profile_name": profile_name, + }, + connection_list_params.ConnectionListParams, + ), + ), + model=ManagedAuth, + ) + + async def delete( + 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, + ) -> None: + """Deletes managed auth and terminates its workflow. + + This will: + + - Delete the managed auth record + - Terminate the Temporal workflow + - Cancel any in-progress login flows + + 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}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/auth/connections/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def follow( + 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, + ) -> AsyncStream[ConnectionFollowResponse]: + """ + Establishes a Server-Sent Events (SSE) stream that delivers real-time login flow + state updates. The stream terminates automatically once the flow reaches a + terminal state (SUCCESS, FAILED, EXPIRED, CANCELED). + + 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}") + extra_headers = {"Accept": "text/event-stream", **(extra_headers or {})} + return await self._get( + f"/auth/connections/{id}/events", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=cast( + Any, ConnectionFollowResponse + ), # Union types cannot be passed in as arguments in the type system + stream=True, + stream_cls=AsyncStream[ConnectionFollowResponse], + ) + + async def login( + self, + id: str, + *, + save_credential_as: 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, + ) -> LoginResponse: + """Starts a login flow for the managed auth. + + Returns immediately with a hosted URL + for the user to complete authentication, or triggers automatic re-auth if + credentials are stored. + + Args: + save_credential_as: If provided, saves credentials under this name upon successful login + + 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._post( + f"/auth/connections/{id}/login", + body=await async_maybe_transform( + {"save_credential_as": save_credential_as}, connection_login_params.ConnectionLoginParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LoginResponse, + ) + + async def submit( + self, + id: str, + *, + fields: Dict[str, str], + mfa_option_id: str | Omit = omit, + sso_button_selector: 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, + ) -> SubmitFieldsResponse: + """Submits field values for the login form. + + Poll the managed auth to track progress + and get results. + + Args: + fields: Map of field name to value + + mfa_option_id: Optional MFA option ID if user selected an MFA method + + sso_button_selector: Optional XPath selector if user chose to click an SSO button instead + + 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._post( + f"/auth/connections/{id}/submit", + body=await async_maybe_transform( + { + "fields": fields, + "mfa_option_id": mfa_option_id, + "sso_button_selector": sso_button_selector, + }, + connection_submit_params.ConnectionSubmitParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SubmitFieldsResponse, + ) + + +class ConnectionsResourceWithRawResponse: + def __init__(self, connections: ConnectionsResource) -> None: + self._connections = connections + + self.create = to_raw_response_wrapper( + connections.create, + ) + self.retrieve = to_raw_response_wrapper( + connections.retrieve, + ) + self.list = to_raw_response_wrapper( + connections.list, + ) + self.delete = to_raw_response_wrapper( + connections.delete, + ) + self.follow = to_raw_response_wrapper( + connections.follow, + ) + self.login = to_raw_response_wrapper( + connections.login, + ) + self.submit = to_raw_response_wrapper( + connections.submit, + ) + + +class AsyncConnectionsResourceWithRawResponse: + def __init__(self, connections: AsyncConnectionsResource) -> None: + self._connections = connections + + self.create = async_to_raw_response_wrapper( + connections.create, + ) + self.retrieve = async_to_raw_response_wrapper( + connections.retrieve, + ) + self.list = async_to_raw_response_wrapper( + connections.list, + ) + self.delete = async_to_raw_response_wrapper( + connections.delete, + ) + self.follow = async_to_raw_response_wrapper( + connections.follow, + ) + self.login = async_to_raw_response_wrapper( + connections.login, + ) + self.submit = async_to_raw_response_wrapper( + connections.submit, + ) + + +class ConnectionsResourceWithStreamingResponse: + def __init__(self, connections: ConnectionsResource) -> None: + self._connections = connections + + self.create = to_streamed_response_wrapper( + connections.create, + ) + self.retrieve = to_streamed_response_wrapper( + connections.retrieve, + ) + self.list = to_streamed_response_wrapper( + connections.list, + ) + self.delete = to_streamed_response_wrapper( + connections.delete, + ) + self.follow = to_streamed_response_wrapper( + connections.follow, + ) + self.login = to_streamed_response_wrapper( + connections.login, + ) + self.submit = to_streamed_response_wrapper( + connections.submit, + ) + + +class AsyncConnectionsResourceWithStreamingResponse: + def __init__(self, connections: AsyncConnectionsResource) -> None: + self._connections = connections + + self.create = async_to_streamed_response_wrapper( + connections.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + connections.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + connections.list, + ) + self.delete = async_to_streamed_response_wrapper( + connections.delete, + ) + self.follow = async_to_streamed_response_wrapper( + connections.follow, + ) + self.login = async_to_streamed_response_wrapper( + connections.login, + ) + self.submit = async_to_streamed_response_wrapper( + connections.submit, + ) diff --git a/src/kernel/resources/credential_providers.py b/src/kernel/resources/credential_providers.py index b1b1248c..8df7d55c 100644 --- a/src/kernel/resources/credential_providers.py +++ b/src/kernel/resources/credential_providers.py @@ -21,6 +21,7 @@ from ..types.credential_provider import CredentialProvider from ..types.credential_provider_test_result import CredentialProviderTestResult from ..types.credential_provider_list_response import CredentialProviderListResponse +from ..types.credential_provider_list_items_response import CredentialProviderListItemsResponse __all__ = ["CredentialProvidersResource", "AsyncCredentialProvidersResource"] @@ -49,6 +50,7 @@ def create( self, *, token: str, + name: str, provider_type: Literal["onepassword"], cache_ttl_seconds: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -65,6 +67,8 @@ def create( Args: token: Service account token for the provider (e.g., 1Password service account token) + name: Human-readable name for this provider instance (unique per org) + provider_type: Type of credential provider cache_ttl_seconds: How long to cache credential lists (default 300 seconds) @@ -78,10 +82,11 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ return self._post( - "/org/credential-providers", + "/org/credential_providers", body=maybe_transform( { "token": token, + "name": name, "provider_type": provider_type, "cache_ttl_seconds": cache_ttl_seconds, }, @@ -119,7 +124,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/org/credential-providers/{id}", + f"/org/credential_providers/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -133,6 +138,7 @@ def update( token: str | Omit = omit, cache_ttl_seconds: int | Omit = omit, enabled: bool | Omit = omit, + name: str | Omit = omit, priority: int | 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. @@ -151,6 +157,8 @@ def update( enabled: Whether the provider is enabled for credential lookups + name: Human-readable name for this provider instance + priority: Priority order for credential lookups (lower numbers are checked first) extra_headers: Send extra headers @@ -164,12 +172,13 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._patch( - f"/org/credential-providers/{id}", + f"/org/credential_providers/{id}", body=maybe_transform( { "token": token, "cache_ttl_seconds": cache_ttl_seconds, "enabled": enabled, + "name": name, "priority": priority, }, credential_provider_update_params.CredentialProviderUpdateParams, @@ -192,7 +201,7 @@ def list( ) -> CredentialProviderListResponse: """List external credential providers configured for the organization.""" return self._get( - "/org/credential-providers", + "/org/credential_providers", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -226,13 +235,47 @@ def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/org/credential-providers/{id}", + f"/org/credential_providers/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=NoneType, ) + def list_items( + 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, + ) -> CredentialProviderListItemsResponse: + """ + Returns available credential items (e.g., 1Password login items) from the + provider. + + 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"/org/credential_providers/{id}/items", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CredentialProviderListItemsResponse, + ) + def test( self, id: str, @@ -259,7 +302,7 @@ def test( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( - f"/org/credential-providers/{id}/test", + f"/org/credential_providers/{id}/test", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -291,6 +334,7 @@ async def create( self, *, token: str, + name: str, provider_type: Literal["onepassword"], cache_ttl_seconds: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -307,6 +351,8 @@ async def create( Args: token: Service account token for the provider (e.g., 1Password service account token) + name: Human-readable name for this provider instance (unique per org) + provider_type: Type of credential provider cache_ttl_seconds: How long to cache credential lists (default 300 seconds) @@ -320,10 +366,11 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ return await self._post( - "/org/credential-providers", + "/org/credential_providers", body=await async_maybe_transform( { "token": token, + "name": name, "provider_type": provider_type, "cache_ttl_seconds": cache_ttl_seconds, }, @@ -361,7 +408,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/org/credential-providers/{id}", + f"/org/credential_providers/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -375,6 +422,7 @@ async def update( token: str | Omit = omit, cache_ttl_seconds: int | Omit = omit, enabled: bool | Omit = omit, + name: str | Omit = omit, priority: int | 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. @@ -393,6 +441,8 @@ async def update( enabled: Whether the provider is enabled for credential lookups + name: Human-readable name for this provider instance + priority: Priority order for credential lookups (lower numbers are checked first) extra_headers: Send extra headers @@ -406,12 +456,13 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._patch( - f"/org/credential-providers/{id}", + f"/org/credential_providers/{id}", body=await async_maybe_transform( { "token": token, "cache_ttl_seconds": cache_ttl_seconds, "enabled": enabled, + "name": name, "priority": priority, }, credential_provider_update_params.CredentialProviderUpdateParams, @@ -434,7 +485,7 @@ async def list( ) -> CredentialProviderListResponse: """List external credential providers configured for the organization.""" return await self._get( - "/org/credential-providers", + "/org/credential_providers", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -468,13 +519,47 @@ async def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/org/credential-providers/{id}", + f"/org/credential_providers/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=NoneType, ) + async def list_items( + 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, + ) -> CredentialProviderListItemsResponse: + """ + Returns available credential items (e.g., 1Password login items) from the + provider. + + 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"/org/credential_providers/{id}/items", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CredentialProviderListItemsResponse, + ) + async def test( self, id: str, @@ -501,7 +586,7 @@ async def test( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( - f"/org/credential-providers/{id}/test", + f"/org/credential_providers/{id}/test", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -528,6 +613,9 @@ def __init__(self, credential_providers: CredentialProvidersResource) -> None: self.delete = to_raw_response_wrapper( credential_providers.delete, ) + self.list_items = to_raw_response_wrapper( + credential_providers.list_items, + ) self.test = to_raw_response_wrapper( credential_providers.test, ) @@ -552,6 +640,9 @@ def __init__(self, credential_providers: AsyncCredentialProvidersResource) -> No self.delete = async_to_raw_response_wrapper( credential_providers.delete, ) + self.list_items = async_to_raw_response_wrapper( + credential_providers.list_items, + ) self.test = async_to_raw_response_wrapper( credential_providers.test, ) @@ -576,6 +667,9 @@ def __init__(self, credential_providers: CredentialProvidersResource) -> None: self.delete = to_streamed_response_wrapper( credential_providers.delete, ) + self.list_items = to_streamed_response_wrapper( + credential_providers.list_items, + ) self.test = to_streamed_response_wrapper( credential_providers.test, ) @@ -600,6 +694,9 @@ def __init__(self, credential_providers: AsyncCredentialProvidersResource) -> No self.delete = async_to_streamed_response_wrapper( credential_providers.delete, ) + self.list_items = async_to_streamed_response_wrapper( + credential_providers.list_items, + ) self.test = async_to_streamed_response_wrapper( credential_providers.test, ) diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py index 7e3ea2d6..6340848f 100644 --- a/src/kernel/types/__init__.py +++ b/src/kernel/types/__init__.py @@ -43,6 +43,7 @@ from .extension_upload_params import ExtensionUploadParams as ExtensionUploadParams from .proxy_retrieve_response import ProxyRetrieveResponse as ProxyRetrieveResponse from .credential_create_params import CredentialCreateParams as CredentialCreateParams +from .credential_provider_item import CredentialProviderItem as CredentialProviderItem from .credential_update_params import CredentialUpdateParams as CredentialUpdateParams from .deployment_create_params import DeploymentCreateParams as DeploymentCreateParams from .deployment_follow_params import DeploymentFollowParams as DeploymentFollowParams @@ -75,6 +76,9 @@ from .credential_provider_list_response import CredentialProviderListResponse as CredentialProviderListResponse from .credential_provider_update_params import CredentialProviderUpdateParams as CredentialProviderUpdateParams from .invocation_list_browsers_response import InvocationListBrowsersResponse as InvocationListBrowsersResponse +from .credential_provider_list_items_response import ( + CredentialProviderListItemsResponse as CredentialProviderListItemsResponse, +) from .extension_download_from_chrome_store_params import ( ExtensionDownloadFromChromeStoreParams as ExtensionDownloadFromChromeStoreParams, ) diff --git a/src/kernel/types/agents/agent_auth_invocation_response.py b/src/kernel/types/agents/agent_auth_invocation_response.py index 98235826..fa755492 100644 --- a/src/kernel/types/agents/agent_auth_invocation_response.py +++ b/src/kernel/types/agents/agent_auth_invocation_response.py @@ -16,8 +16,10 @@ class MfaOption(BaseModel): label: str """The visible option text""" - type: Literal["sms", "call", "email", "totp", "push", "security_key"] - """The MFA delivery method type""" + type: Literal["sms", "call", "email", "totp", "push", "password"] + """ + The MFA delivery method type (includes password for auth method selection pages) + """ description: Optional[str] = None """Additional instructions from the site""" @@ -59,12 +61,11 @@ class AgentAuthInvocationResponse(BaseModel): ] """Current step in the invocation workflow""" - type: Literal["login", "auto_login", "reauth"] - """The invocation type: + type: Literal["login", "reauth"] + """The session type: - - login: First-time authentication - - reauth: Re-authentication for previously authenticated agents - - auto_login: Legacy type (no longer created, kept for backward compatibility) + - login: User-initiated authentication + - reauth: System-triggered re-authentication (via health check) """ error_message: Optional[str] = None diff --git a/src/kernel/types/agents/auth/invocation_submit_params.py b/src/kernel/types/agents/auth/invocation_submit_params.py index 7a9c5aca..dc5ee5f6 100644 --- a/src/kernel/types/agents/auth/invocation_submit_params.py +++ b/src/kernel/types/agents/auth/invocation_submit_params.py @@ -19,8 +19,10 @@ class Variant1(TypedDict, total=False): class Variant2(TypedDict, total=False): - selected_mfa_type: Required[Literal["sms", "call", "email", "totp", "push", "security_key"]] - """The MFA delivery method type""" + selected_mfa_type: Required[Literal["sms", "call", "email", "totp", "push", "password"]] + """ + The MFA delivery method type (includes password for auth method selection pages) + """ InvocationSubmitParams: TypeAlias = Union[Variant0, Variant1, Variant2] diff --git a/src/kernel/types/agents/auth_agent.py b/src/kernel/types/agents/auth_agent.py index 7b23b689..851b55ce 100644 --- a/src/kernel/types/agents/auth_agent.py +++ b/src/kernel/types/agents/auth_agent.py @@ -6,7 +6,29 @@ from ..._models import BaseModel -__all__ = ["AuthAgent"] +__all__ = ["AuthAgent", "Credential"] + + +class Credential(BaseModel): + """Reference to credentials for managed auth. + + Use one of: + - { name } for Kernel credentials + - { provider, path } for external provider item + - { provider, auto: true } for external provider domain lookup + """ + + auto: Optional[bool] = None + """If true, lookup by domain from the specified provider""" + + name: Optional[str] = None + """Kernel credential name""" + + path: Optional[str] = None + """Provider-specific path (e.g., "VaultName/ItemName" for 1Password)""" + + provider: Optional[str] = None + """External provider name (e.g., "my-1p")""" class AuthAgent(BaseModel): @@ -31,6 +53,21 @@ class AuthAgent(BaseModel): Additional domains that are valid for this auth agent's authentication flow (besides the primary domain). Useful when login pages redirect to different domains. + + The following SSO/OAuth provider domains are automatically allowed by default + and do not need to be specified: + + - Google: accounts.google.com + - Microsoft/Azure AD: login.microsoftonline.com, login.live.com + - Okta: _.okta.com, _.oktapreview.com + - Auth0: _.auth0.com, _.us.auth0.com, _.eu.auth0.com, _.au.auth0.com + - Apple: appleid.apple.com + - GitHub: github.com + - Facebook/Meta: www.facebook.com + - LinkedIn: www.linkedin.com + - Amazon Cognito: \\**.amazoncognito.com + - OneLogin: \\**.onelogin.com + - Ping Identity: _.pingone.com, _.pingidentity.com """ can_reauth: Optional[bool] = None @@ -39,11 +76,19 @@ class AuthAgent(BaseModel): and login_url) """ - credential_id: Optional[str] = None - """ID of the linked credential for automatic re-authentication""" + credential: Optional[Credential] = None + """Reference to credentials for managed auth. Use one of: + + - { name } for Kernel credentials + - { provider, path } for external provider item + - { provider, auto: true } for external provider domain lookup + """ - credential_name: Optional[str] = None - """Name of the linked credential for automatic re-authentication""" + credential_id: Optional[str] = None + """ + ID of the linked Kernel credential for automatic re-authentication (deprecated, + use credential) + """ has_selectors: Optional[bool] = None """ 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 6027f4d8..fc5d8dba 100644 --- a/src/kernel/types/agents/auth_agent_invocation_create_response.py +++ b/src/kernel/types/agents/auth_agent_invocation_create_response.py @@ -23,10 +23,9 @@ class AuthAgentInvocationCreateResponse(BaseModel): invocation_id: str """Unique identifier for the invocation.""" - type: Literal["login", "auto_login", "reauth"] - """The invocation type: + type: Literal["login", "reauth"] + """The session type: - - login: First-time authentication - - reauth: Re-authentication for previously authenticated agents - - auto_login: Legacy type (no longer created, kept for backward compatibility) + - login: User-initiated authentication + - reauth: System-triggered re-authentication (via health check) """ diff --git a/src/kernel/types/agents/auth_create_params.py b/src/kernel/types/agents/auth_create_params.py index b792d566..613f0034 100644 --- a/src/kernel/types/agents/auth_create_params.py +++ b/src/kernel/types/agents/auth_create_params.py @@ -21,6 +21,21 @@ class AuthCreateParams(TypedDict, total=False): Additional domains that are valid for this auth agent's authentication flow (besides the primary domain). Useful when login pages redirect to different domains. + + The following SSO/OAuth provider domains are automatically allowed by default + and do not need to be specified: + + - Google: accounts.google.com + - Microsoft/Azure AD: login.microsoftonline.com, login.live.com + - Okta: _.okta.com, _.oktapreview.com + - Auth0: _.auth0.com, _.us.auth0.com, _.eu.auth0.com, _.au.auth0.com + - Apple: appleid.apple.com + - GitHub: github.com + - Facebook/Meta: www.facebook.com + - LinkedIn: www.linkedin.com + - Amazon Cognito: \\**.amazoncognito.com + - OneLogin: \\**.onelogin.com + - Ping Identity: _.pingone.com, _.pingidentity.com """ credential_name: str diff --git a/src/kernel/types/agents/discovered_field.py b/src/kernel/types/agents/discovered_field.py index 72ac2949..4dbf53a8 100644 --- a/src/kernel/types/agents/discovered_field.py +++ b/src/kernel/types/agents/discovered_field.py @@ -23,6 +23,12 @@ class DiscoveredField(BaseModel): type: Literal["text", "email", "password", "tel", "number", "url", "code", "totp"] """Field type""" + linked_mfa_type: Optional[Literal["sms", "call", "email", "totp", "push", "password"]] = None + """ + If this field is associated with an MFA option, the type of that option (e.g., + password field linked to "Enter password" option) + """ + placeholder: Optional[str] = None """Field placeholder""" diff --git a/src/kernel/types/auth/__init__.py b/src/kernel/types/auth/__init__.py new file mode 100644 index 00000000..51e505bf --- /dev/null +++ b/src/kernel/types/auth/__init__.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .managed_auth import ManagedAuth as ManagedAuth +from .login_response import LoginResponse as LoginResponse +from .connection_list_params import ConnectionListParams as ConnectionListParams +from .submit_fields_response import SubmitFieldsResponse as SubmitFieldsResponse +from .connection_login_params import ConnectionLoginParams as ConnectionLoginParams +from .connection_create_params import ConnectionCreateParams as ConnectionCreateParams +from .connection_submit_params import ConnectionSubmitParams as ConnectionSubmitParams +from .connection_follow_response import ConnectionFollowResponse as ConnectionFollowResponse diff --git a/src/kernel/types/auth/connection_create_params.py b/src/kernel/types/auth/connection_create_params.py new file mode 100644 index 00000000..9282f48d --- /dev/null +++ b/src/kernel/types/auth/connection_create_params.py @@ -0,0 +1,89 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["ConnectionCreateParams", "Credential", "Proxy"] + + +class ConnectionCreateParams(TypedDict, total=False): + domain: Required[str] + """Domain for authentication""" + + profile_name: Required[str] + """Name of the profile to manage authentication for""" + + allowed_domains: SequenceNotStr[str] + """Additional domains valid for this auth flow (besides the primary domain). + + Useful when login pages redirect to different domains. + + The following SSO/OAuth provider domains are automatically allowed by default + and do not need to be specified: + + - Google: accounts.google.com + - Microsoft/Azure AD: login.microsoftonline.com, login.live.com + - Okta: _.okta.com, _.oktapreview.com + - Auth0: _.auth0.com, _.us.auth0.com, _.eu.auth0.com, _.au.auth0.com + - Apple: appleid.apple.com + - GitHub: github.com + - Facebook/Meta: www.facebook.com + - LinkedIn: www.linkedin.com + - Amazon Cognito: \\**.amazoncognito.com + - OneLogin: \\**.onelogin.com + - Ping Identity: _.pingone.com, _.pingidentity.com + """ + + credential: Credential + """Reference to credentials for managed auth. Use one of: + + - { name } for Kernel credentials + - { provider, path } for external provider item + - { provider, auto: true } for external provider domain lookup + """ + + health_check_interval: int + """Interval in seconds between automatic health checks. + + When set, the system periodically verifies the authentication status and + triggers re-authentication if needed. Must be between 300 (5 minutes) and 86400 + (24 hours). Default is 3600 (1 hour). + """ + + login_url: str + """Optional login page URL to skip discovery""" + + proxy: Proxy + """Optional proxy configuration""" + + +class Credential(TypedDict, total=False): + """Reference to credentials for managed auth. + + Use one of: + - { name } for Kernel credentials + - { provider, path } for external provider item + - { provider, auto: true } for external provider domain lookup + """ + + auto: bool + """If true, lookup by domain from the specified provider""" + + name: str + """Kernel credential name""" + + path: str + """Provider-specific path (e.g., "VaultName/ItemName" for 1Password)""" + + provider: str + """External provider name (e.g., "my-1p")""" + + +class Proxy(TypedDict, total=False): + """Optional proxy configuration""" + + proxy_id: str + """ID of the proxy to use""" diff --git a/src/kernel/types/auth/connection_follow_response.py b/src/kernel/types/auth/connection_follow_response.py new file mode 100644 index 00000000..488ec81a --- /dev/null +++ b/src/kernel/types/auth/connection_follow_response.py @@ -0,0 +1,109 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from ..shared.error_event import ErrorEvent +from ..shared.heartbeat_event import HeartbeatEvent +from ..agents.discovered_field import DiscoveredField + +__all__ = [ + "ConnectionFollowResponse", + "ManagedAuthStateEvent", + "ManagedAuthStateEventMfaOption", + "ManagedAuthStateEventPendingSSOButton", +] + + +class ManagedAuthStateEventMfaOption(BaseModel): + """An MFA method option for verification""" + + label: str + """The visible option text""" + + type: Literal["sms", "call", "email", "totp", "push", "password"] + """ + The MFA delivery method type (includes password for auth method selection pages) + """ + + description: Optional[str] = None + """Additional instructions from the site""" + + target: Optional[str] = None + """The masked destination (phone/email) if shown""" + + +class ManagedAuthStateEventPendingSSOButton(BaseModel): + """An SSO button for signing in with an external identity provider""" + + label: str + """Visible button text""" + + provider: str + """Identity provider name""" + + selector: str + """XPath selector for the button""" + + +class ManagedAuthStateEvent(BaseModel): + """An event representing the current state of a managed auth flow.""" + + event: Literal["managed_auth_state"] + """Event type identifier (always "managed_auth_state").""" + + flow_status: Literal["IN_PROGRESS", "SUCCESS", "FAILED", "EXPIRED", "CANCELED"] + """Current flow status.""" + + flow_step: Literal["DISCOVERING", "AWAITING_INPUT", "AWAITING_EXTERNAL_ACTION", "SUBMITTING", "COMPLETED"] + """Current step in the flow.""" + + timestamp: datetime + """Time the state was reported.""" + + discovered_fields: Optional[List[DiscoveredField]] = None + """Fields awaiting input (present when flow_step=AWAITING_INPUT).""" + + error_message: Optional[str] = None + """Error message (present when flow_status=FAILED).""" + + external_action_message: Optional[str] = None + """ + Instructions for external action (present when + flow_step=AWAITING_EXTERNAL_ACTION). + """ + + flow_type: Optional[Literal["LOGIN", "REAUTH"]] = None + """Type of the current flow.""" + + hosted_url: Optional[str] = None + """URL to redirect user to for hosted login.""" + + live_view_url: Optional[str] = None + """Browser live view URL for debugging.""" + + mfa_options: Optional[List[ManagedAuthStateEventMfaOption]] = None + """ + MFA method options (present when flow_step=AWAITING_INPUT and MFA selection + required). + """ + + pending_sso_buttons: Optional[List[ManagedAuthStateEventPendingSSOButton]] = None + """SSO buttons available (present when flow_step=AWAITING_INPUT).""" + + post_login_url: Optional[str] = None + """URL where the browser landed after successful login.""" + + website_error: Optional[str] = None + """Visible error message from the website (e.g., 'Incorrect password'). + + Present when the website displays an error during login. + """ + + +ConnectionFollowResponse: TypeAlias = Annotated[ + Union[ManagedAuthStateEvent, ErrorEvent, HeartbeatEvent], PropertyInfo(discriminator="event") +] diff --git a/src/kernel/types/auth/connection_list_params.py b/src/kernel/types/auth/connection_list_params.py new file mode 100644 index 00000000..dd8d0de2 --- /dev/null +++ b/src/kernel/types/auth/connection_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__ = ["ConnectionListParams"] + + +class ConnectionListParams(TypedDict, total=False): + domain: str + """Filter by domain""" + + limit: int + """Maximum number of results to return""" + + offset: int + """Number of results to skip""" + + profile_name: str + """Filter by profile name""" diff --git a/src/kernel/types/auth/connection_login_params.py b/src/kernel/types/auth/connection_login_params.py new file mode 100644 index 00000000..a30d9ff4 --- /dev/null +++ b/src/kernel/types/auth/connection_login_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 TypedDict + +__all__ = ["ConnectionLoginParams"] + + +class ConnectionLoginParams(TypedDict, total=False): + save_credential_as: str + """If provided, saves credentials under this name upon successful login""" diff --git a/src/kernel/types/auth/connection_submit_params.py b/src/kernel/types/auth/connection_submit_params.py new file mode 100644 index 00000000..b299e0a5 --- /dev/null +++ b/src/kernel/types/auth/connection_submit_params.py @@ -0,0 +1,19 @@ +# 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__ = ["ConnectionSubmitParams"] + + +class ConnectionSubmitParams(TypedDict, total=False): + fields: Required[Dict[str, str]] + """Map of field name to value""" + + mfa_option_id: str + """Optional MFA option ID if user selected an MFA method""" + + sso_button_selector: str + """Optional XPath selector if user chose to click an SSO button instead""" diff --git a/src/kernel/types/auth/login_response.py b/src/kernel/types/auth/login_response.py new file mode 100644 index 00000000..178c3005 --- /dev/null +++ b/src/kernel/types/auth/login_response.py @@ -0,0 +1,31 @@ +# 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 + +__all__ = ["LoginResponse"] + + +class LoginResponse(BaseModel): + """Response from starting a login flow""" + + id: str + """Managed auth ID""" + + flow_expires_at: datetime + """When the login flow expires""" + + flow_type: Literal["LOGIN", "REAUTH"] + """Type of login flow started""" + + hosted_url: str + """URL to redirect user to for login""" + + handoff_code: Optional[str] = None + """One-time code for handoff (internal use)""" + + live_view_url: Optional[str] = None + """Browser live view URL for watching the login flow""" diff --git a/src/kernel/types/auth/managed_auth.py b/src/kernel/types/auth/managed_auth.py new file mode 100644 index 00000000..849616fb --- /dev/null +++ b/src/kernel/types/auth/managed_auth.py @@ -0,0 +1,181 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel +from ..agents.discovered_field import DiscoveredField + +__all__ = ["ManagedAuth", "Credential", "MfaOption", "PendingSSOButton"] + + +class Credential(BaseModel): + """Reference to credentials for managed auth. + + Use one of: + - { name } for Kernel credentials + - { provider, path } for external provider item + - { provider, auto: true } for external provider domain lookup + """ + + auto: Optional[bool] = None + """If true, lookup by domain from the specified provider""" + + name: Optional[str] = None + """Kernel credential name""" + + path: Optional[str] = None + """Provider-specific path (e.g., "VaultName/ItemName" for 1Password)""" + + provider: Optional[str] = None + """External provider name (e.g., "my-1p")""" + + +class MfaOption(BaseModel): + """An MFA method option for verification""" + + label: str + """The visible option text""" + + type: Literal["sms", "call", "email", "totp", "push", "password"] + """ + The MFA delivery method type (includes password for auth method selection pages) + """ + + description: Optional[str] = None + """Additional instructions from the site""" + + target: Optional[str] = None + """The masked destination (phone/email) if shown""" + + +class PendingSSOButton(BaseModel): + """An SSO button for signing in with an external identity provider""" + + label: str + """Visible button text""" + + provider: str + """Identity provider name""" + + selector: str + """XPath selector for the button""" + + +class ManagedAuth(BaseModel): + """Managed authentication that keeps a profile logged into a specific domain. + + Flow fields (flow_status, flow_step, discovered_fields, mfa_options) reflect the most recent login flow and are null when no flow has been initiated. + """ + + id: str + """Unique identifier for the managed auth""" + + domain: str + """Target domain for authentication""" + + profile_name: str + """Name of the profile associated with this managed auth""" + + status: Literal["AUTHENTICATED", "NEEDS_AUTH"] + """Current authentication status of the managed profile""" + + allowed_domains: Optional[List[str]] = None + """ + Additional domains that are valid for this auth flow (besides the primary + domain). Useful when login pages redirect to different domains. + + The following SSO/OAuth provider domains are automatically allowed by default + and do not need to be specified: + + - Google: accounts.google.com + - Microsoft/Azure AD: login.microsoftonline.com, login.live.com + - Okta: _.okta.com, _.oktapreview.com + - Auth0: _.auth0.com, _.us.auth0.com, _.eu.auth0.com, _.au.auth0.com + - Apple: appleid.apple.com + - GitHub: github.com + - Facebook/Meta: www.facebook.com + - LinkedIn: www.linkedin.com + - Amazon Cognito: \\**.amazoncognito.com + - OneLogin: \\**.onelogin.com + - Ping Identity: _.pingone.com, _.pingidentity.com + """ + + can_reauth: Optional[bool] = None + """ + Whether automatic re-authentication is possible (has credential, selectors, and + login_url) + """ + + credential: Optional[Credential] = None + """Reference to credentials for managed auth. Use one of: + + - { name } for Kernel credentials + - { provider, path } for external provider item + - { provider, auto: true } for external provider domain lookup + """ + + discovered_fields: Optional[List[DiscoveredField]] = None + """Fields awaiting input (present when flow_step=awaiting_input)""" + + error_message: Optional[str] = None + """Error message (present when flow_status=failed)""" + + external_action_message: Optional[str] = None + """ + Instructions for external action (present when + flow_step=awaiting_external_action) + """ + + flow_expires_at: Optional[datetime] = None + """When the current flow expires (null when no flow in progress)""" + + flow_status: Optional[Literal["IN_PROGRESS", "SUCCESS", "FAILED", "EXPIRED", "CANCELED"]] = None + """Current flow status (null when no flow in progress)""" + + flow_step: Optional[ + Literal["DISCOVERING", "AWAITING_INPUT", "AWAITING_EXTERNAL_ACTION", "SUBMITTING", "COMPLETED"] + ] = None + """Current step in the flow (null when no flow in progress)""" + + flow_type: Optional[Literal["LOGIN", "REAUTH"]] = None + """Type of the current flow (null when no flow in progress)""" + + health_check_interval: Optional[int] = None + """Interval in seconds between automatic health checks. + + When set, the system periodically verifies the authentication status and + triggers re-authentication if needed. Must be between 300 (5 minutes) and 86400 + (24 hours). Default is 3600 (1 hour). + """ + + hosted_url: Optional[str] = None + """URL to redirect user to for hosted login (present when flow in progress)""" + + last_auth_at: Optional[datetime] = None + """When the profile was last successfully authenticated""" + + live_view_url: Optional[str] = None + """Browser live view URL for debugging (present when flow in progress)""" + + mfa_options: Optional[List[MfaOption]] = None + """ + MFA method options (present when flow_step=awaiting_input and MFA selection + required) + """ + + pending_sso_buttons: Optional[List[PendingSSOButton]] = None + """SSO buttons available (present when flow_step=awaiting_input)""" + + post_login_url: Optional[str] = None + """URL where the browser landed after successful login""" + + sso_provider: Optional[str] = None + """SSO provider being used (e.g., google, github, microsoft)""" + + website_error: Optional[str] = None + """Visible error message from the website (e.g., 'Incorrect password'). + + Present when the website displays an error during login. + """ diff --git a/src/kernel/types/auth/submit_fields_response.py b/src/kernel/types/auth/submit_fields_response.py new file mode 100644 index 00000000..1133c1b4 --- /dev/null +++ b/src/kernel/types/auth/submit_fields_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["SubmitFieldsResponse"] + + +class SubmitFieldsResponse(BaseModel): + """Response from submitting field values""" + + accepted: bool + """Whether the submission was accepted for processing""" diff --git a/src/kernel/types/credential_provider.py b/src/kernel/types/credential_provider.py index 83a205ad..866f0631 100644 --- a/src/kernel/types/credential_provider.py +++ b/src/kernel/types/credential_provider.py @@ -22,6 +22,9 @@ class CredentialProvider(BaseModel): enabled: bool """Whether the provider is enabled for credential lookups""" + name: str + """Human-readable name for this provider instance""" + priority: int """Priority order for credential lookups (lower numbers are checked first)""" diff --git a/src/kernel/types/credential_provider_create_params.py b/src/kernel/types/credential_provider_create_params.py index ed631b39..ea531c7d 100644 --- a/src/kernel/types/credential_provider_create_params.py +++ b/src/kernel/types/credential_provider_create_params.py @@ -11,6 +11,9 @@ class CredentialProviderCreateParams(TypedDict, total=False): token: Required[str] """Service account token for the provider (e.g., 1Password service account token)""" + name: Required[str] + """Human-readable name for this provider instance (unique per org)""" + provider_type: Required[Literal["onepassword"]] """Type of credential provider""" diff --git a/src/kernel/types/credential_provider_item.py b/src/kernel/types/credential_provider_item.py new file mode 100644 index 00000000..8bc618b5 --- /dev/null +++ b/src/kernel/types/credential_provider_item.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["CredentialProviderItem"] + + +class CredentialProviderItem(BaseModel): + """A credential item from an external provider (e.g., a 1Password login item)""" + + id: str + """Unique identifier for the item within the provider""" + + path: str + """Path to reference this item (VaultName/ItemTitle format)""" + + title: str + """Display name of the credential item""" + + vault_id: str + """ID of the vault containing this item""" + + vault_name: str + """Name of the vault containing this item""" + + urls: Optional[List[str]] = None + """URLs associated with this credential""" diff --git a/src/kernel/types/credential_provider_list_items_response.py b/src/kernel/types/credential_provider_list_items_response.py new file mode 100644 index 00000000..265f2ef2 --- /dev/null +++ b/src/kernel/types/credential_provider_list_items_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel +from .credential_provider_item import CredentialProviderItem + +__all__ = ["CredentialProviderListItemsResponse"] + + +class CredentialProviderListItemsResponse(BaseModel): + items: Optional[List[CredentialProviderItem]] = None diff --git a/src/kernel/types/credential_provider_update_params.py b/src/kernel/types/credential_provider_update_params.py index ecebeab7..b2dda02b 100644 --- a/src/kernel/types/credential_provider_update_params.py +++ b/src/kernel/types/credential_provider_update_params.py @@ -17,5 +17,8 @@ class CredentialProviderUpdateParams(TypedDict, total=False): enabled: bool """Whether the provider is enabled for credential lookups""" + name: str + """Human-readable name for this provider instance""" + priority: int """Priority order for credential lookups (lower numbers are checked first)""" diff --git a/tests/api_resources/agents/auth/test_invocations.py b/tests/api_resources/agents/auth/test_invocations.py index 6d70dfac..829a6076 100644 --- a/tests/api_resources/agents/auth/test_invocations.py +++ b/tests/api_resources/agents/auth/test_invocations.py @@ -14,6 +14,8 @@ InvocationExchangeResponse, ) +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -23,26 +25,31 @@ class TestInvocations: @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", - ) + with pytest.warns(DeprecationWarning): + 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_method_create_with_all_params(self, client: Kernel) -> None: - invocation = client.agents.auth.invocations.create( - auth_agent_id="abc123xyz", - save_credential_as="my-netflix-login", - ) + with pytest.warns(DeprecationWarning): + invocation = client.agents.auth.invocations.create( + auth_agent_id="abc123xyz", + save_credential_as="my-netflix-login", + ) + 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", - ) + with pytest.warns(DeprecationWarning): + 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" @@ -52,31 +59,35 @@ def test_raw_response_create(self, client: Kernel) -> None: @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" + with pytest.warns(DeprecationWarning): + 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"]) + 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: - invocation = client.agents.auth.invocations.retrieve( - "invocation_id", - ) + with pytest.warns(DeprecationWarning): + 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", - ) + with pytest.warns(DeprecationWarning): + 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" @@ -86,41 +97,46 @@ def test_raw_response_retrieve(self, client: Kernel) -> None: @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" + with pytest.warns(DeprecationWarning): + 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"]) + 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( - "", - ) + with pytest.warns(DeprecationWarning): + 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_exchange(self, client: Kernel) -> None: - invocation = client.agents.auth.invocations.exchange( - invocation_id="invocation_id", - code="abc123xyz", - ) + with pytest.warns(DeprecationWarning): + 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", - ) + with pytest.warns(DeprecationWarning): + 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" @@ -130,49 +146,54 @@ def test_raw_response_exchange(self, client: Kernel) -> None: @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" + with pytest.warns(DeprecationWarning): + 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"]) + 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", - ) + with pytest.warns(DeprecationWarning): + 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_overload_1(self, client: Kernel) -> None: - invocation = client.agents.auth.invocations.submit( - invocation_id="invocation_id", - field_values={ - "email": "user@example.com", - "password": "********", - }, - ) + with pytest.warns(DeprecationWarning): + 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_overload_1(self, client: Kernel) -> None: - response = client.agents.auth.invocations.with_raw_response.submit( - invocation_id="invocation_id", - field_values={ - "email": "user@example.com", - "password": "********", - }, - ) + with pytest.warns(DeprecationWarning): + 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" @@ -182,49 +203,54 @@ def test_raw_response_submit_overload_1(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_submit_overload_1(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"]) + with pytest.warns(DeprecationWarning): + 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_overload_1(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": "********", - }, - ) + with pytest.warns(DeprecationWarning): + 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": "********", + }, + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_submit_overload_2(self, client: Kernel) -> None: - invocation = client.agents.auth.invocations.submit( - invocation_id="invocation_id", - sso_button="xpath=//button[contains(text(), 'Continue with Google')]", - ) + with pytest.warns(DeprecationWarning): + invocation = client.agents.auth.invocations.submit( + invocation_id="invocation_id", + sso_button="xpath=//button[contains(text(), 'Continue with Google')]", + ) + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_submit_overload_2(self, client: Kernel) -> None: - response = client.agents.auth.invocations.with_raw_response.submit( - invocation_id="invocation_id", - sso_button="xpath=//button[contains(text(), 'Continue with Google')]", - ) + with pytest.warns(DeprecationWarning): + response = client.agents.auth.invocations.with_raw_response.submit( + invocation_id="invocation_id", + sso_button="xpath=//button[contains(text(), 'Continue with Google')]", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -234,43 +260,48 @@ def test_raw_response_submit_overload_2(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_submit_overload_2(self, client: Kernel) -> None: - with client.agents.auth.invocations.with_streaming_response.submit( - invocation_id="invocation_id", - sso_button="xpath=//button[contains(text(), 'Continue with Google')]", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.agents.auth.invocations.with_streaming_response.submit( + invocation_id="invocation_id", + sso_button="xpath=//button[contains(text(), 'Continue with Google')]", + ) 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"]) + 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_overload_2(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="", - sso_button="xpath=//button[contains(text(), 'Continue with Google')]", - ) + with pytest.warns(DeprecationWarning): + 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="", + sso_button="xpath=//button[contains(text(), 'Continue with Google')]", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_submit_overload_3(self, client: Kernel) -> None: - invocation = client.agents.auth.invocations.submit( - invocation_id="invocation_id", - selected_mfa_type="sms", - ) + with pytest.warns(DeprecationWarning): + invocation = client.agents.auth.invocations.submit( + invocation_id="invocation_id", + selected_mfa_type="sms", + ) + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_submit_overload_3(self, client: Kernel) -> None: - response = client.agents.auth.invocations.with_raw_response.submit( - invocation_id="invocation_id", - selected_mfa_type="sms", - ) + with pytest.warns(DeprecationWarning): + response = client.agents.auth.invocations.with_raw_response.submit( + invocation_id="invocation_id", + selected_mfa_type="sms", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -280,26 +311,28 @@ def test_raw_response_submit_overload_3(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_submit_overload_3(self, client: Kernel) -> None: - with client.agents.auth.invocations.with_streaming_response.submit( - invocation_id="invocation_id", - selected_mfa_type="sms", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.agents.auth.invocations.with_streaming_response.submit( + invocation_id="invocation_id", + selected_mfa_type="sms", + ) 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"]) + 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_overload_3(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="", - selected_mfa_type="sms", - ) + with pytest.warns(DeprecationWarning): + 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="", + selected_mfa_type="sms", + ) class TestAsyncInvocations: @@ -310,26 +343,31 @@ class TestAsyncInvocations: @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", - ) + with pytest.warns(DeprecationWarning): + 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_method_create_with_all_params(self, async_client: AsyncKernel) -> None: - invocation = await async_client.agents.auth.invocations.create( - auth_agent_id="abc123xyz", - save_credential_as="my-netflix-login", - ) + with pytest.warns(DeprecationWarning): + invocation = await async_client.agents.auth.invocations.create( + auth_agent_id="abc123xyz", + save_credential_as="my-netflix-login", + ) + 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", - ) + with pytest.warns(DeprecationWarning): + 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" @@ -339,31 +377,35 @@ async def test_raw_response_create(self, async_client: AsyncKernel) -> None: @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" + with pytest.warns(DeprecationWarning): + 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"]) + 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: - invocation = await async_client.agents.auth.invocations.retrieve( - "invocation_id", - ) + with pytest.warns(DeprecationWarning): + 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", - ) + with pytest.warns(DeprecationWarning): + 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" @@ -373,41 +415,46 @@ async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: @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" + with pytest.warns(DeprecationWarning): + 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"]) + 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( - "", - ) + with pytest.warns(DeprecationWarning): + 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_exchange(self, async_client: AsyncKernel) -> None: - invocation = await async_client.agents.auth.invocations.exchange( - invocation_id="invocation_id", - code="abc123xyz", - ) + with pytest.warns(DeprecationWarning): + 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", - ) + with pytest.warns(DeprecationWarning): + 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" @@ -417,49 +464,54 @@ async def test_raw_response_exchange(self, async_client: AsyncKernel) -> None: @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" + with pytest.warns(DeprecationWarning): + 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"]) + 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", - ) + with pytest.warns(DeprecationWarning): + 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_overload_1(self, async_client: AsyncKernel) -> None: - invocation = await async_client.agents.auth.invocations.submit( - invocation_id="invocation_id", - field_values={ - "email": "user@example.com", - "password": "********", - }, - ) + with pytest.warns(DeprecationWarning): + 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_overload_1(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": "********", - }, - ) + with pytest.warns(DeprecationWarning): + 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" @@ -469,49 +521,54 @@ async def test_raw_response_submit_overload_1(self, async_client: AsyncKernel) - @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_submit_overload_1(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"]) + with pytest.warns(DeprecationWarning): + 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_overload_1(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": "********", - }, - ) + with pytest.warns(DeprecationWarning): + 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": "********", + }, + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_submit_overload_2(self, async_client: AsyncKernel) -> None: - invocation = await async_client.agents.auth.invocations.submit( - invocation_id="invocation_id", - sso_button="xpath=//button[contains(text(), 'Continue with Google')]", - ) + with pytest.warns(DeprecationWarning): + invocation = await async_client.agents.auth.invocations.submit( + invocation_id="invocation_id", + sso_button="xpath=//button[contains(text(), 'Continue with Google')]", + ) + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_submit_overload_2(self, async_client: AsyncKernel) -> None: - response = await async_client.agents.auth.invocations.with_raw_response.submit( - invocation_id="invocation_id", - sso_button="xpath=//button[contains(text(), 'Continue with Google')]", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.agents.auth.invocations.with_raw_response.submit( + invocation_id="invocation_id", + sso_button="xpath=//button[contains(text(), 'Continue with Google')]", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -521,43 +578,48 @@ async def test_raw_response_submit_overload_2(self, async_client: AsyncKernel) - @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_submit_overload_2(self, async_client: AsyncKernel) -> None: - async with async_client.agents.auth.invocations.with_streaming_response.submit( - invocation_id="invocation_id", - sso_button="xpath=//button[contains(text(), 'Continue with Google')]", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.agents.auth.invocations.with_streaming_response.submit( + invocation_id="invocation_id", + sso_button="xpath=//button[contains(text(), 'Continue with Google')]", + ) 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"]) + 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_overload_2(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="", - sso_button="xpath=//button[contains(text(), 'Continue with Google')]", - ) + with pytest.warns(DeprecationWarning): + 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="", + sso_button="xpath=//button[contains(text(), 'Continue with Google')]", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_submit_overload_3(self, async_client: AsyncKernel) -> None: - invocation = await async_client.agents.auth.invocations.submit( - invocation_id="invocation_id", - selected_mfa_type="sms", - ) + with pytest.warns(DeprecationWarning): + invocation = await async_client.agents.auth.invocations.submit( + invocation_id="invocation_id", + selected_mfa_type="sms", + ) + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_submit_overload_3(self, async_client: AsyncKernel) -> None: - response = await async_client.agents.auth.invocations.with_raw_response.submit( - invocation_id="invocation_id", - selected_mfa_type="sms", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.agents.auth.invocations.with_raw_response.submit( + invocation_id="invocation_id", + selected_mfa_type="sms", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -567,23 +629,25 @@ async def test_raw_response_submit_overload_3(self, async_client: AsyncKernel) - @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_submit_overload_3(self, async_client: AsyncKernel) -> None: - async with async_client.agents.auth.invocations.with_streaming_response.submit( - invocation_id="invocation_id", - selected_mfa_type="sms", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.agents.auth.invocations.with_streaming_response.submit( + invocation_id="invocation_id", + selected_mfa_type="sms", + ) 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"]) + 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_overload_3(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="", - selected_mfa_type="sms", - ) + with pytest.warns(DeprecationWarning): + 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="", + selected_mfa_type="sms", + ) diff --git a/tests/api_resources/agents/test_auth.py b/tests/api_resources/agents/test_auth.py index 9855ef85..c64d77d4 100644 --- a/tests/api_resources/agents/test_auth.py +++ b/tests/api_resources/agents/test_auth.py @@ -12,6 +12,8 @@ from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination from kernel.types.agents import AuthAgent +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,32 +23,37 @@ class TestAuth: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Kernel) -> None: - auth = client.agents.auth.create( - domain="netflix.com", - profile_name="user-123", - ) + with pytest.warns(DeprecationWarning): + auth = client.agents.auth.create( + domain="netflix.com", + profile_name="user-123", + ) + 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( - domain="netflix.com", - profile_name="user-123", - allowed_domains=["login.netflix.com", "auth.netflix.com"], - credential_name="my-netflix-login", - login_url="https://netflix.com/login", - proxy={"proxy_id": "proxy_id"}, - ) + with pytest.warns(DeprecationWarning): + auth = client.agents.auth.create( + domain="netflix.com", + profile_name="user-123", + allowed_domains=["login.netflix.com", "auth.netflix.com"], + credential_name="my-netflix-login", + 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( - domain="netflix.com", - profile_name="user-123", - ) + with pytest.warns(DeprecationWarning): + response = client.agents.auth.with_raw_response.create( + domain="netflix.com", + profile_name="user-123", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -56,32 +63,36 @@ def test_raw_response_create(self, client: Kernel) -> None: @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( - domain="netflix.com", - profile_name="user-123", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.agents.auth.with_streaming_response.create( + domain="netflix.com", + profile_name="user-123", + ) 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"]) + 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: - auth = client.agents.auth.retrieve( - "id", - ) + with pytest.warns(DeprecationWarning): + 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", - ) + with pytest.warns(DeprecationWarning): + 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" @@ -91,46 +102,53 @@ def test_raw_response_retrieve(self, client: Kernel) -> None: @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" + with pytest.warns(DeprecationWarning): + 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"]) + 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( - "", - ) + with pytest.warns(DeprecationWarning): + 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_list(self, client: Kernel) -> None: - auth = client.agents.auth.list() + with pytest.warns(DeprecationWarning): + 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_list_with_all_params(self, client: Kernel) -> None: - auth = client.agents.auth.list( - domain="domain", - limit=100, - offset=0, - profile_name="profile_name", - ) + with pytest.warns(DeprecationWarning): + auth = client.agents.auth.list( + domain="domain", + limit=100, + offset=0, + profile_name="profile_name", + ) + assert_matches_type(SyncOffsetPagination[AuthAgent], auth, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list(self, client: Kernel) -> None: - response = client.agents.auth.with_raw_response.list() + with pytest.warns(DeprecationWarning): + response = client.agents.auth.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -140,29 +158,33 @@ def test_raw_response_list(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize 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" + with pytest.warns(DeprecationWarning): + 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(SyncOffsetPagination[AuthAgent], auth, path=["response"]) + auth = response.parse() + assert_matches_type(SyncOffsetPagination[AuthAgent], auth, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_delete(self, client: Kernel) -> None: - auth = client.agents.auth.delete( - "id", - ) + with pytest.warns(DeprecationWarning): + auth = client.agents.auth.delete( + "id", + ) + assert auth is None @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_delete(self, client: Kernel) -> None: - response = client.agents.auth.with_raw_response.delete( - "id", - ) + with pytest.warns(DeprecationWarning): + response = client.agents.auth.with_raw_response.delete( + "id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -172,24 +194,26 @@ 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.agents.auth.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.agents.auth.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth = response.parse() - assert auth is None + auth = response.parse() + assert auth is None assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_delete(self, client: Kernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.agents.auth.with_raw_response.delete( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.agents.auth.with_raw_response.delete( + "", + ) class TestAsyncAuth: @@ -200,32 +224,37 @@ class TestAsyncAuth: @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( - domain="netflix.com", - profile_name="user-123", - ) + with pytest.warns(DeprecationWarning): + auth = await async_client.agents.auth.create( + domain="netflix.com", + profile_name="user-123", + ) + 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( - domain="netflix.com", - profile_name="user-123", - allowed_domains=["login.netflix.com", "auth.netflix.com"], - credential_name="my-netflix-login", - login_url="https://netflix.com/login", - proxy={"proxy_id": "proxy_id"}, - ) + with pytest.warns(DeprecationWarning): + auth = await async_client.agents.auth.create( + domain="netflix.com", + profile_name="user-123", + allowed_domains=["login.netflix.com", "auth.netflix.com"], + credential_name="my-netflix-login", + 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( - domain="netflix.com", - profile_name="user-123", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.agents.auth.with_raw_response.create( + domain="netflix.com", + profile_name="user-123", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -235,32 +264,36 @@ async def test_raw_response_create(self, async_client: AsyncKernel) -> None: @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( - domain="netflix.com", - profile_name="user-123", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.agents.auth.with_streaming_response.create( + domain="netflix.com", + profile_name="user-123", + ) 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"]) + 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: - auth = await async_client.agents.auth.retrieve( - "id", - ) + with pytest.warns(DeprecationWarning): + 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", - ) + with pytest.warns(DeprecationWarning): + 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" @@ -270,46 +303,53 @@ async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: @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" + with pytest.warns(DeprecationWarning): + 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"]) + 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( - "", - ) + with pytest.warns(DeprecationWarning): + 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_list(self, async_client: AsyncKernel) -> None: - auth = await async_client.agents.auth.list() + with pytest.warns(DeprecationWarning): + 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_list_with_all_params(self, async_client: AsyncKernel) -> None: - auth = await async_client.agents.auth.list( - domain="domain", - limit=100, - offset=0, - profile_name="profile_name", - ) + with pytest.warns(DeprecationWarning): + auth = await async_client.agents.auth.list( + domain="domain", + limit=100, + offset=0, + profile_name="profile_name", + ) + assert_matches_type(AsyncOffsetPagination[AuthAgent], auth, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncKernel) -> None: - response = await async_client.agents.auth.with_raw_response.list() + with pytest.warns(DeprecationWarning): + 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" @@ -319,29 +359,33 @@ async def test_raw_response_list(self, async_client: AsyncKernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize 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" + with pytest.warns(DeprecationWarning): + 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(AsyncOffsetPagination[AuthAgent], auth, path=["response"]) + auth = await response.parse() + assert_matches_type(AsyncOffsetPagination[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_delete(self, async_client: AsyncKernel) -> None: - auth = await async_client.agents.auth.delete( - "id", - ) + with pytest.warns(DeprecationWarning): + auth = await async_client.agents.auth.delete( + "id", + ) + assert auth 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.agents.auth.with_raw_response.delete( - "id", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.agents.auth.with_raw_response.delete( + "id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -351,21 +395,23 @@ 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.agents.auth.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.agents.auth.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth = await response.parse() - assert auth is None + auth = await response.parse() + assert auth is None assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_delete(self, async_client: AsyncKernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.agents.auth.with_raw_response.delete( - "", - ) + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.agents.auth.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/auth/__init__.py b/tests/api_resources/auth/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/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/auth/test_connections.py b/tests/api_resources/auth/test_connections.py new file mode 100644 index 00000000..96ba0a08 --- /dev/null +++ b/tests/api_resources/auth/test_connections.py @@ -0,0 +1,715 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from kernel import Kernel, AsyncKernel +from tests.utils import assert_matches_type +from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination +from kernel.types.auth import ( + ManagedAuth, + LoginResponse, + SubmitFieldsResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestConnections: + 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: + connection = client.auth.connections.create( + domain="netflix.com", + profile_name="user-123", + ) + assert_matches_type(ManagedAuth, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Kernel) -> None: + connection = client.auth.connections.create( + domain="netflix.com", + profile_name="user-123", + allowed_domains=["login.netflix.com", "auth.netflix.com"], + credential={ + "auto": True, + "name": "my-netflix-creds", + "path": "Personal/Netflix", + "provider": "my-1p", + }, + health_check_interval=3600, + login_url="https://netflix.com/login", + proxy={"proxy_id": "proxy_id"}, + ) + assert_matches_type(ManagedAuth, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Kernel) -> None: + response = client.auth.connections.with_raw_response.create( + domain="netflix.com", + profile_name="user-123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert_matches_type(ManagedAuth, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Kernel) -> None: + with client.auth.connections.with_streaming_response.create( + domain="netflix.com", + profile_name="user-123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(ManagedAuth, connection, 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: + connection = client.auth.connections.retrieve( + "id", + ) + assert_matches_type(ManagedAuth, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Kernel) -> None: + response = client.auth.connections.with_raw_response.retrieve( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert_matches_type(ManagedAuth, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Kernel) -> None: + with client.auth.connections.with_streaming_response.retrieve( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(ManagedAuth, connection, 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.auth.connections.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list(self, client: Kernel) -> None: + connection = client.auth.connections.list() + assert_matches_type(SyncOffsetPagination[ManagedAuth], connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Kernel) -> None: + connection = client.auth.connections.list( + domain="domain", + limit=100, + offset=0, + profile_name="profile_name", + ) + assert_matches_type(SyncOffsetPagination[ManagedAuth], connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list(self, client: Kernel) -> None: + response = client.auth.connections.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert_matches_type(SyncOffsetPagination[ManagedAuth], connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Kernel) -> None: + with client.auth.connections.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(SyncOffsetPagination[ManagedAuth], connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_delete(self, client: Kernel) -> None: + connection = client.auth.connections.delete( + "id", + ) + assert connection is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Kernel) -> None: + response = client.auth.connections.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert connection is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Kernel) -> None: + with client.auth.connections.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert connection is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_delete(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.auth.connections.with_raw_response.delete( + "", + ) + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_method_follow(self, client: Kernel) -> None: + connection_stream = client.auth.connections.follow( + "id", + ) + connection_stream.response.close() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_raw_response_follow(self, client: Kernel) -> None: + response = client.auth.connections.with_raw_response.follow( + "id", + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_streaming_response_follow(self, client: Kernel) -> None: + with client.auth.connections.with_streaming_response.follow( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + def test_path_params_follow(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.auth.connections.with_raw_response.follow( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_login(self, client: Kernel) -> None: + connection = client.auth.connections.login( + id="id", + ) + assert_matches_type(LoginResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_login_with_all_params(self, client: Kernel) -> None: + connection = client.auth.connections.login( + id="id", + save_credential_as="my-netflix-login", + ) + assert_matches_type(LoginResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_login(self, client: Kernel) -> None: + response = client.auth.connections.with_raw_response.login( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert_matches_type(LoginResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_login(self, client: Kernel) -> None: + with client.auth.connections.with_streaming_response.login( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(LoginResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_login(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.auth.connections.with_raw_response.login( + id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_submit(self, client: Kernel) -> None: + connection = client.auth.connections.submit( + id="id", + fields={ + "email": "user@example.com", + "password": "secret", + }, + ) + assert_matches_type(SubmitFieldsResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_submit_with_all_params(self, client: Kernel) -> None: + connection = client.auth.connections.submit( + id="id", + fields={ + "email": "user@example.com", + "password": "secret", + }, + mfa_option_id="sms", + sso_button_selector="xpath=//button[contains(text(), 'Continue with Google')]", + ) + assert_matches_type(SubmitFieldsResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_submit(self, client: Kernel) -> None: + response = client.auth.connections.with_raw_response.submit( + id="id", + fields={ + "email": "user@example.com", + "password": "secret", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert_matches_type(SubmitFieldsResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_submit(self, client: Kernel) -> None: + with client.auth.connections.with_streaming_response.submit( + id="id", + fields={ + "email": "user@example.com", + "password": "secret", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(SubmitFieldsResponse, connection, 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 `id` but received ''"): + client.auth.connections.with_raw_response.submit( + id="", + fields={ + "email": "user@example.com", + "password": "secret", + }, + ) + + +class TestAsyncConnections: + 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_create(self, async_client: AsyncKernel) -> None: + connection = await async_client.auth.connections.create( + domain="netflix.com", + profile_name="user-123", + ) + assert_matches_type(ManagedAuth, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None: + connection = await async_client.auth.connections.create( + domain="netflix.com", + profile_name="user-123", + allowed_domains=["login.netflix.com", "auth.netflix.com"], + credential={ + "auto": True, + "name": "my-netflix-creds", + "path": "Personal/Netflix", + "provider": "my-1p", + }, + health_check_interval=3600, + login_url="https://netflix.com/login", + proxy={"proxy_id": "proxy_id"}, + ) + assert_matches_type(ManagedAuth, connection, 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.auth.connections.with_raw_response.create( + domain="netflix.com", + profile_name="user-123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert_matches_type(ManagedAuth, connection, 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.auth.connections.with_streaming_response.create( + domain="netflix.com", + profile_name="user-123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(ManagedAuth, connection, 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: + connection = await async_client.auth.connections.retrieve( + "id", + ) + assert_matches_type(ManagedAuth, connection, 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.auth.connections.with_raw_response.retrieve( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert_matches_type(ManagedAuth, connection, 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.auth.connections.with_streaming_response.retrieve( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(ManagedAuth, connection, 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.auth.connections.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncKernel) -> None: + connection = await async_client.auth.connections.list() + assert_matches_type(AsyncOffsetPagination[ManagedAuth], connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None: + connection = await async_client.auth.connections.list( + domain="domain", + limit=100, + offset=0, + profile_name="profile_name", + ) + assert_matches_type(AsyncOffsetPagination[ManagedAuth], connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncKernel) -> None: + response = await async_client.auth.connections.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert_matches_type(AsyncOffsetPagination[ManagedAuth], connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncKernel) -> None: + async with async_client.auth.connections.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(AsyncOffsetPagination[ManagedAuth], connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncKernel) -> None: + connection = await async_client.auth.connections.delete( + "id", + ) + assert connection 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.auth.connections.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert connection is 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.auth.connections.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert connection is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.auth.connections.with_raw_response.delete( + "", + ) + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_method_follow(self, async_client: AsyncKernel) -> None: + connection_stream = await async_client.auth.connections.follow( + "id", + ) + await connection_stream.response.aclose() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_raw_response_follow(self, async_client: AsyncKernel) -> None: + response = await async_client.auth.connections.with_raw_response.follow( + "id", + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = await response.parse() + await stream.close() + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_streaming_response_follow(self, async_client: AsyncKernel) -> None: + async with async_client.auth.connections.with_streaming_response.follow( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism doesn't support text/event-stream responses") + @parametrize + async def test_path_params_follow(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.auth.connections.with_raw_response.follow( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_login(self, async_client: AsyncKernel) -> None: + connection = await async_client.auth.connections.login( + id="id", + ) + assert_matches_type(LoginResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_login_with_all_params(self, async_client: AsyncKernel) -> None: + connection = await async_client.auth.connections.login( + id="id", + save_credential_as="my-netflix-login", + ) + assert_matches_type(LoginResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_login(self, async_client: AsyncKernel) -> None: + response = await async_client.auth.connections.with_raw_response.login( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert_matches_type(LoginResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_login(self, async_client: AsyncKernel) -> None: + async with async_client.auth.connections.with_streaming_response.login( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(LoginResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_login(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.auth.connections.with_raw_response.login( + id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_submit(self, async_client: AsyncKernel) -> None: + connection = await async_client.auth.connections.submit( + id="id", + fields={ + "email": "user@example.com", + "password": "secret", + }, + ) + assert_matches_type(SubmitFieldsResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_submit_with_all_params(self, async_client: AsyncKernel) -> None: + connection = await async_client.auth.connections.submit( + id="id", + fields={ + "email": "user@example.com", + "password": "secret", + }, + mfa_option_id="sms", + sso_button_selector="xpath=//button[contains(text(), 'Continue with Google')]", + ) + assert_matches_type(SubmitFieldsResponse, connection, 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.auth.connections.with_raw_response.submit( + id="id", + fields={ + "email": "user@example.com", + "password": "secret", + }, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert_matches_type(SubmitFieldsResponse, connection, 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.auth.connections.with_streaming_response.submit( + id="id", + fields={ + "email": "user@example.com", + "password": "secret", + }, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(SubmitFieldsResponse, connection, 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 `id` but received ''"): + await async_client.auth.connections.with_raw_response.submit( + id="", + fields={ + "email": "user@example.com", + "password": "secret", + }, + ) diff --git a/tests/api_resources/test_credential_providers.py b/tests/api_resources/test_credential_providers.py index 136446c0..f110523f 100644 --- a/tests/api_resources/test_credential_providers.py +++ b/tests/api_resources/test_credential_providers.py @@ -13,6 +13,7 @@ CredentialProvider, CredentialProviderTestResult, CredentialProviderListResponse, + CredentialProviderListItemsResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -26,6 +27,7 @@ class TestCredentialProviders: def test_method_create(self, client: Kernel) -> None: credential_provider = client.credential_providers.create( token="ops_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", + name="my-1password", provider_type="onepassword", ) assert_matches_type(CredentialProvider, credential_provider, path=["response"]) @@ -35,6 +37,7 @@ def test_method_create(self, client: Kernel) -> None: def test_method_create_with_all_params(self, client: Kernel) -> None: credential_provider = client.credential_providers.create( token="ops_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", + name="my-1password", provider_type="onepassword", cache_ttl_seconds=300, ) @@ -45,6 +48,7 @@ def test_method_create_with_all_params(self, client: Kernel) -> None: def test_raw_response_create(self, client: Kernel) -> None: response = client.credential_providers.with_raw_response.create( token="ops_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", + name="my-1password", provider_type="onepassword", ) @@ -58,6 +62,7 @@ def test_raw_response_create(self, client: Kernel) -> None: def test_streaming_response_create(self, client: Kernel) -> None: with client.credential_providers.with_streaming_response.create( token="ops_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", + name="my-1password", provider_type="onepassword", ) as response: assert not response.is_closed @@ -126,6 +131,7 @@ def test_method_update_with_all_params(self, client: Kernel) -> None: token="ops_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", cache_ttl_seconds=300, enabled=True, + name="my-1password", priority=0, ) assert_matches_type(CredentialProvider, credential_provider, path=["response"]) @@ -234,6 +240,48 @@ def test_path_params_delete(self, client: Kernel) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list_items(self, client: Kernel) -> None: + credential_provider = client.credential_providers.list_items( + "id", + ) + assert_matches_type(CredentialProviderListItemsResponse, credential_provider, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list_items(self, client: Kernel) -> None: + response = client.credential_providers.with_raw_response.list_items( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential_provider = response.parse() + assert_matches_type(CredentialProviderListItemsResponse, credential_provider, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list_items(self, client: Kernel) -> None: + with client.credential_providers.with_streaming_response.list_items( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential_provider = response.parse() + assert_matches_type(CredentialProviderListItemsResponse, credential_provider, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_list_items(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.credential_providers.with_raw_response.list_items( + "", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_test(self, client: Kernel) -> None: @@ -287,6 +335,7 @@ class TestAsyncCredentialProviders: async def test_method_create(self, async_client: AsyncKernel) -> None: credential_provider = await async_client.credential_providers.create( token="ops_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", + name="my-1password", provider_type="onepassword", ) assert_matches_type(CredentialProvider, credential_provider, path=["response"]) @@ -296,6 +345,7 @@ async def test_method_create(self, async_client: AsyncKernel) -> None: async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None: credential_provider = await async_client.credential_providers.create( token="ops_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", + name="my-1password", provider_type="onepassword", cache_ttl_seconds=300, ) @@ -306,6 +356,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> async def test_raw_response_create(self, async_client: AsyncKernel) -> None: response = await async_client.credential_providers.with_raw_response.create( token="ops_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", + name="my-1password", provider_type="onepassword", ) @@ -319,6 +370,7 @@ async def test_raw_response_create(self, async_client: AsyncKernel) -> None: async def test_streaming_response_create(self, async_client: AsyncKernel) -> None: async with async_client.credential_providers.with_streaming_response.create( token="ops_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", + name="my-1password", provider_type="onepassword", ) as response: assert not response.is_closed @@ -387,6 +439,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> token="ops_eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", cache_ttl_seconds=300, enabled=True, + name="my-1password", priority=0, ) assert_matches_type(CredentialProvider, credential_provider, path=["response"]) @@ -495,6 +548,48 @@ async def test_path_params_delete(self, async_client: AsyncKernel) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list_items(self, async_client: AsyncKernel) -> None: + credential_provider = await async_client.credential_providers.list_items( + "id", + ) + assert_matches_type(CredentialProviderListItemsResponse, credential_provider, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list_items(self, async_client: AsyncKernel) -> None: + response = await async_client.credential_providers.with_raw_response.list_items( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential_provider = await response.parse() + assert_matches_type(CredentialProviderListItemsResponse, credential_provider, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list_items(self, async_client: AsyncKernel) -> None: + async with async_client.credential_providers.with_streaming_response.list_items( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential_provider = await response.parse() + assert_matches_type(CredentialProviderListItemsResponse, credential_provider, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_list_items(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.credential_providers.with_raw_response.list_items( + "", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_test(self, async_client: AsyncKernel) -> None: From ae57cddcc741113d043d630f88d577ab2f1e5654 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 20:32:50 +0000 Subject: [PATCH 2/2] release: 0.31.1 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- src/kernel/_version.py | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f81bf992..8305d4ab 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.31.0" + ".": "0.31.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e7fc045..b0d111a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.31.1 (2026-02-06) + +Full Changelog: [v0.31.0...v0.31.1](https://github.com/kernel/kernel-python-sdk/compare/v0.31.0...v0.31.1) + +### Chores + +* add Managed Auth API planning doc ([f24a387](https://github.com/kernel/kernel-python-sdk/commit/f24a387432ff8e506c143da566e65d5674c7ff6c)) + ## 0.31.0 (2026-02-06) Full Changelog: [v0.30.0...v0.31.0](https://github.com/kernel/kernel-python-sdk/compare/v0.30.0...v0.31.0) diff --git a/pyproject.toml b/pyproject.toml index 898d5dfd..e00a5130 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.31.0" +version = "0.31.1" 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 4d628238..a0977c94 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.31.0" # x-release-please-version +__version__ = "0.31.1" # x-release-please-version