diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d2d60a3..a36746b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.24.0" + ".": "0.25.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 99bb6c4..434275e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 89 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-13214b99e392aab631aa1ca99b6a51a58df81e34156d21b8d639bea779566123.yml -openapi_spec_hash: a88d175fc3980de3097ac1411d8dcbff -config_hash: 179f33af31ece83563163d5b3d751d13 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-8d66dbedea5b240936b338809f272568ca84a452fc13dbda835479f2ec068b41.yml +openapi_spec_hash: 7c499bfce2e996f1fff5e7791cea390e +config_hash: fcc2db3ed48ab4e8d1b588d31d394a23 diff --git a/CHANGELOG.md b/CHANGELOG.md index dbcf083..e8130e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 0.25.0 (2026-01-07) + +Full Changelog: [v0.24.0...v0.25.0](https://github.com/onkernel/kernel-python-sdk/compare/v0.24.0...v0.25.0) + +### Features + +* **api:** add health check endpoint for proxies ([105801c](https://github.com/onkernel/kernel-python-sdk/commit/105801ca8028df379b2f79a8a5bd4c4e9f43c3cc)) +* **auth:** add auto_login credential flow ([e941d0f](https://github.com/onkernel/kernel-python-sdk/commit/e941d0fb0a62cb8a1aad2424577c825bd6764df4)) +* Enhance AuthAgentInvocation with step and last activity tracking ([99b8057](https://github.com/onkernel/kernel-python-sdk/commit/99b80574482322b22a5bd1ce79762471330f4945)) + + +### Bug Fixes + +* use async_to_httpx_files in patch method ([b103991](https://github.com/onkernel/kernel-python-sdk/commit/b1039911a4b2b47df0087821aae2cb0ddc374de2)) + + +### Chores + +* **internal:** add `--fix` argument to lint script ([73e0e46](https://github.com/onkernel/kernel-python-sdk/commit/73e0e4621186edb18b05514b14a638d62a0e5d14)) +* **internal:** codegen related update ([6f18d40](https://github.com/onkernel/kernel-python-sdk/commit/6f18d40b0f1f98b0f50ffb64b7fe858aaefb872f)) + ## 0.24.0 (2025-12-17) Full Changelog: [v0.23.0...v0.24.0](https://github.com/onkernel/kernel-python-sdk/compare/v0.23.0...v0.24.0) diff --git a/LICENSE b/LICENSE index b32a077..3b7d20d 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Kernel + Copyright 2026 Kernel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/api.md b/api.md index fb67de5..db5f2df 100644 --- a/api.md +++ b/api.md @@ -228,7 +228,12 @@ Methods: Types: ```python -from kernel.types import ProxyCreateResponse, ProxyRetrieveResponse, ProxyListResponse +from kernel.types import ( + ProxyCreateResponse, + ProxyRetrieveResponse, + ProxyListResponse, + ProxyCheckResponse, +) ``` Methods: @@ -237,6 +242,7 @@ Methods: - client.proxies.retrieve(id) -> ProxyRetrieveResponse - client.proxies.list() -> ProxyListResponse - client.proxies.delete(id) -> None +- client.proxies.check(id) -> ProxyCheckResponse # Extensions @@ -281,7 +287,6 @@ Types: ```python from kernel.types.agents import ( - AgentAuthDiscoverResponse, AgentAuthInvocationResponse, AgentAuthSubmitResponse, AuthAgent, @@ -289,7 +294,6 @@ from kernel.types.agents import ( AuthAgentInvocationCreateRequest, AuthAgentInvocationCreateResponse, DiscoveredField, - ReauthResponse, ) ``` @@ -299,7 +303,6 @@ Methods: - client.agents.auth.retrieve(id) -> AuthAgent - client.agents.auth.list(\*\*params) -> SyncOffsetPagination[AuthAgent] - client.agents.auth.delete(id) -> None -- client.agents.auth.reauth(id) -> ReauthResponse ### Invocations @@ -313,7 +316,6 @@ Methods: - client.agents.auth.invocations.create(\*\*params) -> AuthAgentInvocationCreateResponse - client.agents.auth.invocations.retrieve(invocation_id) -> AgentAuthInvocationResponse -- client.agents.auth.invocations.discover(invocation_id, \*\*params) -> AgentAuthDiscoverResponse - client.agents.auth.invocations.exchange(invocation_id, \*\*params) -> InvocationExchangeResponse - client.agents.auth.invocations.submit(invocation_id, \*\*params) -> AgentAuthSubmitResponse @@ -322,13 +324,19 @@ Methods: Types: ```python -from kernel.types import CreateCredentialRequest, Credential, UpdateCredentialRequest +from kernel.types import ( + CreateCredentialRequest, + Credential, + UpdateCredentialRequest, + CredentialTotpCodeResponse, +) ``` Methods: - client.credentials.create(\*\*params) -> Credential -- client.credentials.retrieve(id) -> Credential -- client.credentials.update(id, \*\*params) -> Credential +- client.credentials.retrieve(id_or_name) -> Credential +- client.credentials.update(id_or_name, \*\*params) -> Credential - client.credentials.list(\*\*params) -> SyncOffsetPagination[Credential] -- client.credentials.delete(id) -> None +- client.credentials.delete(id_or_name) -> None +- client.credentials.totp_code(id_or_name) -> CredentialTotpCodeResponse diff --git a/pyproject.toml b/pyproject.toml index 770392c..716de1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.24.0" +version = "0.25.0" description = "The official Python library for the kernel API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/scripts/lint b/scripts/lint index b5b8891..7675e60 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,8 +4,13 @@ set -e cd "$(dirname "$0")/.." -echo "==> Running lints" -rye run lint +if [ "$1" = "--fix" ]; then + echo "==> Running lints with --fix" + rye run fix:ruff +else + echo "==> Running lints" + rye run lint +fi echo "==> Making sure it imports" rye run python -c 'import kernel' diff --git a/src/kernel/_base_client.py b/src/kernel/_base_client.py index e55218b..787be54 100644 --- a/src/kernel/_base_client.py +++ b/src/kernel/_base_client.py @@ -1774,7 +1774,7 @@ async def patch( options: RequestOptions = {}, ) -> ResponseT: opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts) diff --git a/src/kernel/_version.py b/src/kernel/_version.py index 17d46b5..d65bad2 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.24.0" # x-release-please-version +__version__ = "0.25.0" # x-release-please-version diff --git a/src/kernel/resources/agents/auth/auth.py b/src/kernel/resources/agents/auth/auth.py index 39f22fc..f4a0276 100644 --- a/src/kernel/resources/agents/auth/auth.py +++ b/src/kernel/resources/agents/auth/auth.py @@ -4,7 +4,7 @@ import httpx -from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +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 .invocations import ( @@ -26,7 +26,6 @@ from ...._base_client import AsyncPaginator, make_request_options from ....types.agents import auth_list_params, auth_create_params from ....types.agents.auth_agent import AuthAgent -from ....types.agents.reauth_response import ReauthResponse __all__ = ["AuthResource", "AsyncAuthResource"] @@ -58,8 +57,9 @@ def with_streaming_response(self) -> AuthResourceWithStreamingResponse: def create( self, *, + domain: str, profile_name: str, - target_domain: str, + allowed_domains: SequenceNotStr[str] | Omit = omit, credential_name: str | Omit = omit, login_url: str | Omit = omit, proxy: auth_create_params.Proxy | Omit = omit, @@ -77,9 +77,13 @@ def create( invocation - use POST /agents/auth/invocations to start an auth flow. Args: + domain: Domain for authentication + profile_name: Name of the profile to use for this auth agent - target_domain: Target domain for authentication + allowed_domains: Additional domains that are valid for this auth agent's authentication flow + (besides the primary domain). Useful when login pages redirect to different + domains. 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 @@ -102,8 +106,9 @@ def create( "/agents/auth", body=maybe_transform( { + "domain": domain, "profile_name": profile_name, - "target_domain": target_domain, + "allowed_domains": allowed_domains, "credential_name": credential_name, "login_url": login_url, "proxy": proxy, @@ -154,10 +159,10 @@ def retrieve( def list( self, *, + domain: str | Omit = omit, limit: int | Omit = omit, offset: int | Omit = omit, profile_name: str | Omit = omit, - target_domain: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -166,17 +171,17 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncOffsetPagination[AuthAgent]: """ - List auth agents with optional filters for profile_name and target_domain. + List auth agents 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 - target_domain: Filter by target domain - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -195,10 +200,10 @@ def list( timeout=timeout, query=maybe_transform( { + "domain": domain, "limit": limit, "offset": offset, "profile_name": profile_name, - "target_domain": target_domain, }, auth_list_params.AuthListParams, ), @@ -245,42 +250,6 @@ def delete( cast_to=NoneType, ) - def reauth( - 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, - ) -> ReauthResponse: - """ - Triggers automatic re-authentication for an auth agent using stored credentials. - Requires the auth agent to have a linked credential, stored selectors, and - login_url. Returns immediately with status indicating whether re-auth was - started. - - 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._post( - f"/agents/auth/{id}/reauth", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ReauthResponse, - ) - class AsyncAuthResource(AsyncAPIResource): @cached_property @@ -309,8 +278,9 @@ def with_streaming_response(self) -> AsyncAuthResourceWithStreamingResponse: async def create( self, *, + domain: str, profile_name: str, - target_domain: str, + allowed_domains: SequenceNotStr[str] | Omit = omit, credential_name: str | Omit = omit, login_url: str | Omit = omit, proxy: auth_create_params.Proxy | Omit = omit, @@ -328,9 +298,13 @@ async def create( invocation - use POST /agents/auth/invocations to start an auth flow. Args: + domain: Domain for authentication + profile_name: Name of the profile to use for this auth agent - target_domain: Target domain for authentication + allowed_domains: Additional domains that are valid for this auth agent's authentication flow + (besides the primary domain). Useful when login pages redirect to different + domains. 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 @@ -353,8 +327,9 @@ async def create( "/agents/auth", body=await async_maybe_transform( { + "domain": domain, "profile_name": profile_name, - "target_domain": target_domain, + "allowed_domains": allowed_domains, "credential_name": credential_name, "login_url": login_url, "proxy": proxy, @@ -405,10 +380,10 @@ async def retrieve( def list( self, *, + domain: str | Omit = omit, limit: int | Omit = omit, offset: int | Omit = omit, profile_name: str | Omit = omit, - target_domain: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -417,17 +392,17 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[AuthAgent, AsyncOffsetPagination[AuthAgent]]: """ - List auth agents with optional filters for profile_name and target_domain. + List auth agents 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 - target_domain: Filter by target domain - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -446,10 +421,10 @@ def list( timeout=timeout, query=maybe_transform( { + "domain": domain, "limit": limit, "offset": offset, "profile_name": profile_name, - "target_domain": target_domain, }, auth_list_params.AuthListParams, ), @@ -496,42 +471,6 @@ async def delete( cast_to=NoneType, ) - async def reauth( - 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, - ) -> ReauthResponse: - """ - Triggers automatic re-authentication for an auth agent using stored credentials. - Requires the auth agent to have a linked credential, stored selectors, and - login_url. Returns immediately with status indicating whether re-auth was - started. - - 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._post( - f"/agents/auth/{id}/reauth", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ReauthResponse, - ) - class AuthResourceWithRawResponse: def __init__(self, auth: AuthResource) -> None: @@ -549,9 +488,6 @@ def __init__(self, auth: AuthResource) -> None: self.delete = to_raw_response_wrapper( auth.delete, ) - self.reauth = to_raw_response_wrapper( - auth.reauth, - ) @cached_property def invocations(self) -> InvocationsResourceWithRawResponse: @@ -574,9 +510,6 @@ def __init__(self, auth: AsyncAuthResource) -> None: self.delete = async_to_raw_response_wrapper( auth.delete, ) - self.reauth = async_to_raw_response_wrapper( - auth.reauth, - ) @cached_property def invocations(self) -> AsyncInvocationsResourceWithRawResponse: @@ -599,9 +532,6 @@ def __init__(self, auth: AuthResource) -> None: self.delete = to_streamed_response_wrapper( auth.delete, ) - self.reauth = to_streamed_response_wrapper( - auth.reauth, - ) @cached_property def invocations(self) -> InvocationsResourceWithStreamingResponse: @@ -624,9 +554,6 @@ def __init__(self, auth: AsyncAuthResource) -> None: self.delete = async_to_streamed_response_wrapper( auth.delete, ) - self.reauth = async_to_streamed_response_wrapper( - auth.reauth, - ) @cached_property def invocations(self) -> AsyncInvocationsResourceWithStreamingResponse: diff --git a/src/kernel/resources/agents/auth/invocations.py b/src/kernel/resources/agents/auth/invocations.py index ac2bb8e..34ab614 100644 --- a/src/kernel/resources/agents/auth/invocations.py +++ b/src/kernel/resources/agents/auth/invocations.py @@ -2,12 +2,13 @@ from __future__ import annotations -from typing import Any, Dict, cast +from typing import Dict +from typing_extensions import overload import httpx from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ...._utils import maybe_transform, async_maybe_transform +from ...._utils import required_args, maybe_transform, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( @@ -17,14 +18,8 @@ async_to_streamed_response_wrapper, ) from ...._base_client import make_request_options -from ....types.agents.auth import ( - invocation_create_params, - invocation_submit_params, - invocation_discover_params, - invocation_exchange_params, -) +from ....types.agents.auth import invocation_create_params, invocation_submit_params, invocation_exchange_params from ....types.agents.agent_auth_submit_response import AgentAuthSubmitResponse -from ....types.agents.agent_auth_discover_response import AgentAuthDiscoverResponse from ....types.agents.agent_auth_invocation_response import AgentAuthInvocationResponse from ....types.agents.auth.invocation_exchange_response import InvocationExchangeResponse from ....types.agents.auth_agent_invocation_create_response import AuthAgentInvocationCreateResponse @@ -85,24 +80,19 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ - return cast( - AuthAgentInvocationCreateResponse, - self._post( - "/agents/auth/invocations", - body=maybe_transform( - { - "auth_agent_id": auth_agent_id, - "save_credential_as": save_credential_as, - }, - invocation_create_params.InvocationCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=cast( - Any, AuthAgentInvocationCreateResponse - ), # Union types cannot be passed in as arguments in the type system + return self._post( + "/agents/auth/invocations", + body=maybe_transform( + { + "auth_agent_id": auth_agent_id, + "save_credential_as": save_credential_as, + }, + invocation_create_params.InvocationCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), + cast_to=AuthAgentInvocationCreateResponse, ) def retrieve( @@ -116,10 +106,10 @@ def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthInvocationResponse: - """Returns invocation details including app_name and target_domain. + """Returns invocation details including status, app_name, and domain. - Uses the JWT - returned by the exchange endpoint, or standard API key or JWT authentication. + Supports both + API key and JWT (from exchange endpoint) authentication. Args: extra_headers: Send extra headers @@ -140,26 +130,25 @@ def retrieve( cast_to=AgentAuthInvocationResponse, ) - def discover( + def exchange( self, invocation_id: str, *, - login_url: str | Omit = omit, + code: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AgentAuthDiscoverResponse: - """ - Inspects the target site to detect logged-in state or discover required fields. - Returns 200 with success: true when fields are found, or 4xx/5xx for failures. - Requires the JWT returned by the exchange endpoint. + ) -> InvocationExchangeResponse: + """Validates the handoff code and returns a JWT token for subsequent requests. + + No + authentication required (the handoff code serves as the credential). Args: - login_url: Optional login page URL. If provided, will override the stored login URL for - this discovery invocation and skip Phase 1 discovery. + code: Handoff code from start endpoint extra_headers: Send extra headers @@ -172,33 +161,35 @@ def discover( if not invocation_id: raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") return self._post( - f"/agents/auth/invocations/{invocation_id}/discover", - body=maybe_transform({"login_url": login_url}, invocation_discover_params.InvocationDiscoverParams), + f"/agents/auth/invocations/{invocation_id}/exchange", + body=maybe_transform({"code": code}, invocation_exchange_params.InvocationExchangeParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AgentAuthDiscoverResponse, + cast_to=InvocationExchangeResponse, ) - def exchange( + @overload + def submit( self, invocation_id: str, *, - code: str, + field_values: Dict[str, str], # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> InvocationExchangeResponse: - """Validates the handoff code and returns a JWT token for subsequent requests. + ) -> AgentAuthSubmitResponse: + """Submits field values for the discovered login form. - No - authentication required (the handoff code serves as the credential). + Returns immediately after + submission is accepted. Poll the invocation endpoint to track progress and get + results. Args: - code: Handoff code from start endpoint + field_values: Values for the discovered login fields extra_headers: Send extra headers @@ -208,22 +199,14 @@ def exchange( timeout: Override the client-level default timeout for this request, in seconds """ - if not invocation_id: - raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") - return self._post( - f"/agents/auth/invocations/{invocation_id}/exchange", - body=maybe_transform({"code": code}, invocation_exchange_params.InvocationExchangeParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=InvocationExchangeResponse, - ) + ... + @overload def submit( self, invocation_id: str, *, - field_values: Dict[str, str], + sso_button: 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, @@ -231,12 +214,14 @@ def submit( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthSubmitResponse: - """ - Submits field values for the discovered login form and may return additional - auth fields or success. Requires the JWT returned by the exchange endpoint. + """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 + sso_button: Selector of SSO button to click extra_headers: Send extra headers @@ -246,11 +231,33 @@ def submit( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + @required_args(["field_values"], ["sso_button"]) + def submit( + self, + invocation_id: str, + *, + field_values: Dict[str, str] | Omit = omit, + sso_button: 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, + ) -> AgentAuthSubmitResponse: if not invocation_id: raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") return self._post( f"/agents/auth/invocations/{invocation_id}/submit", - body=maybe_transform({"field_values": field_values}, invocation_submit_params.InvocationSubmitParams), + body=maybe_transform( + { + "field_values": field_values, + "sso_button": sso_button, + }, + invocation_submit_params.InvocationSubmitParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -311,24 +318,19 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - return cast( - AuthAgentInvocationCreateResponse, - await self._post( - "/agents/auth/invocations", - body=await async_maybe_transform( - { - "auth_agent_id": auth_agent_id, - "save_credential_as": save_credential_as, - }, - invocation_create_params.InvocationCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=cast( - Any, AuthAgentInvocationCreateResponse - ), # Union types cannot be passed in as arguments in the type system + return await self._post( + "/agents/auth/invocations", + body=await async_maybe_transform( + { + "auth_agent_id": auth_agent_id, + "save_credential_as": save_credential_as, + }, + invocation_create_params.InvocationCreateParams, ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AuthAgentInvocationCreateResponse, ) async def retrieve( @@ -342,10 +344,10 @@ async def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AgentAuthInvocationResponse: - """Returns invocation details including app_name and target_domain. + """Returns invocation details including status, app_name, and domain. - Uses the JWT - returned by the exchange endpoint, or standard API key or JWT authentication. + Supports both + API key and JWT (from exchange endpoint) authentication. Args: extra_headers: Send extra headers @@ -366,26 +368,25 @@ async def retrieve( cast_to=AgentAuthInvocationResponse, ) - async def discover( + async def exchange( self, invocation_id: str, *, - login_url: str | Omit = omit, + code: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AgentAuthDiscoverResponse: - """ - Inspects the target site to detect logged-in state or discover required fields. - Returns 200 with success: true when fields are found, or 4xx/5xx for failures. - Requires the JWT returned by the exchange endpoint. + ) -> InvocationExchangeResponse: + """Validates the handoff code and returns a JWT token for subsequent requests. + + No + authentication required (the handoff code serves as the credential). Args: - login_url: Optional login page URL. If provided, will override the stored login URL for - this discovery invocation and skip Phase 1 discovery. + code: Handoff code from start endpoint extra_headers: Send extra headers @@ -398,35 +399,35 @@ async def discover( if not invocation_id: raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") return await self._post( - f"/agents/auth/invocations/{invocation_id}/discover", - body=await async_maybe_transform( - {"login_url": login_url}, invocation_discover_params.InvocationDiscoverParams - ), + f"/agents/auth/invocations/{invocation_id}/exchange", + body=await async_maybe_transform({"code": code}, invocation_exchange_params.InvocationExchangeParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AgentAuthDiscoverResponse, + cast_to=InvocationExchangeResponse, ) - async def exchange( + @overload + async def submit( self, invocation_id: str, *, - code: str, + field_values: Dict[str, str], # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> InvocationExchangeResponse: - """Validates the handoff code and returns a JWT token for subsequent requests. + ) -> AgentAuthSubmitResponse: + """Submits field values for the discovered login form. - No - authentication required (the handoff code serves as the credential). + Returns immediately after + submission is accepted. Poll the invocation endpoint to track progress and get + results. Args: - code: Handoff code from start endpoint + field_values: Values for the discovered login fields extra_headers: Send extra headers @@ -436,22 +437,14 @@ async def exchange( timeout: Override the client-level default timeout for this request, in seconds """ - if not invocation_id: - raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") - return await self._post( - f"/agents/auth/invocations/{invocation_id}/exchange", - body=await async_maybe_transform({"code": code}, invocation_exchange_params.InvocationExchangeParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=InvocationExchangeResponse, - ) + ... + @overload async def submit( self, invocation_id: str, *, - field_values: Dict[str, str], + sso_button: 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, @@ -459,12 +452,14 @@ 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 and may return additional - auth fields or success. Requires the JWT returned by the exchange endpoint. + """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 + sso_button: Selector of SSO button to click extra_headers: Send extra headers @@ -474,12 +469,32 @@ async def submit( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + @required_args(["field_values"], ["sso_button"]) + async def submit( + self, + invocation_id: str, + *, + field_values: Dict[str, str] | Omit = omit, + sso_button: 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, + ) -> AgentAuthSubmitResponse: if not invocation_id: raise ValueError(f"Expected a non-empty value for `invocation_id` but received {invocation_id!r}") return await self._post( f"/agents/auth/invocations/{invocation_id}/submit", body=await async_maybe_transform( - {"field_values": field_values}, invocation_submit_params.InvocationSubmitParams + { + "field_values": field_values, + "sso_button": sso_button, + }, + invocation_submit_params.InvocationSubmitParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -498,9 +513,6 @@ def __init__(self, invocations: InvocationsResource) -> None: self.retrieve = to_raw_response_wrapper( invocations.retrieve, ) - self.discover = to_raw_response_wrapper( - invocations.discover, - ) self.exchange = to_raw_response_wrapper( invocations.exchange, ) @@ -519,9 +531,6 @@ def __init__(self, invocations: AsyncInvocationsResource) -> None: self.retrieve = async_to_raw_response_wrapper( invocations.retrieve, ) - self.discover = async_to_raw_response_wrapper( - invocations.discover, - ) self.exchange = async_to_raw_response_wrapper( invocations.exchange, ) @@ -540,9 +549,6 @@ def __init__(self, invocations: InvocationsResource) -> None: self.retrieve = to_streamed_response_wrapper( invocations.retrieve, ) - self.discover = to_streamed_response_wrapper( - invocations.discover, - ) self.exchange = to_streamed_response_wrapper( invocations.exchange, ) @@ -561,9 +567,6 @@ def __init__(self, invocations: AsyncInvocationsResource) -> None: self.retrieve = async_to_streamed_response_wrapper( invocations.retrieve, ) - self.discover = async_to_streamed_response_wrapper( - invocations.discover, - ) self.exchange = async_to_streamed_response_wrapper( invocations.exchange, ) diff --git a/src/kernel/resources/credentials.py b/src/kernel/resources/credentials.py index 91dafce..85e0c8a 100644 --- a/src/kernel/resources/credentials.py +++ b/src/kernel/resources/credentials.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict +from typing import Dict, Optional import httpx @@ -20,6 +20,7 @@ from ..pagination import SyncOffsetPagination, AsyncOffsetPagination from .._base_client import AsyncPaginator, make_request_options from ..types.credential import Credential +from ..types.credential_totp_code_response import CredentialTotpCodeResponse __all__ = ["CredentialsResource", "AsyncCredentialsResource"] @@ -50,6 +51,8 @@ def create( domain: str, name: str, values: Dict[str, str], + sso_provider: str | Omit = omit, + totp_secret: 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, @@ -57,10 +60,8 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Credential: - """Create a new credential for storing login information. - - Values are encrypted at - rest. + """ + Create a new credential for storing login information. Args: domain: Target domain this credential is for @@ -69,6 +70,14 @@ def create( values: Field name to value mapping (e.g., username, password) + sso_provider: If set, indicates this credential should be used with the specified SSO provider + (e.g., google, github, microsoft). When the target site has a matching SSO + button, it will be clicked first before filling credential values on the + identity provider's login page. + + totp_secret: Base32-encoded TOTP secret for generating one-time passwords. Used for automatic + 2FA during login. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -84,6 +93,8 @@ def create( "domain": domain, "name": name, "values": values, + "sso_provider": sso_provider, + "totp_secret": totp_secret, }, credential_create_params.CredentialCreateParams, ), @@ -95,7 +106,7 @@ def create( def retrieve( self, - id: str, + id_or_name: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -104,7 +115,7 @@ def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Credential: - """Retrieve a credential by its ID. + """Retrieve a credential by its ID or name. Credential values are not returned. @@ -117,10 +128,10 @@ def retrieve( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return self._get( - f"/credentials/{id}", + f"/credentials/{id_or_name}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -129,9 +140,11 @@ def retrieve( def update( self, - id: str, + id_or_name: str, *, name: str | Omit = omit, + sso_provider: Optional[str] | Omit = omit, + totp_secret: str | Omit = omit, values: Dict[str, str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -142,13 +155,20 @@ def update( ) -> Credential: """Update a credential's name or values. - Values are encrypted at rest. + When values are provided, they are merged + with existing values (new keys are added, existing keys are overwritten). Args: name: New name for the credential - values: Field name to value mapping (e.g., username, password). Replaces all existing - values. + sso_provider: If set, indicates this credential should be used with the specified SSO + provider. Set to empty string or null to remove. + + totp_secret: Base32-encoded TOTP secret for generating one-time passwords. Spaces and + formatting are automatically normalized. Set to empty string to remove. + + values: Field name to value mapping. Values are merged with existing values (new keys + added, existing keys overwritten). extra_headers: Send extra headers @@ -158,13 +178,15 @@ def update( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return self._patch( - f"/credentials/{id}", + f"/credentials/{id_or_name}", body=maybe_transform( { "name": name, + "sso_provider": sso_provider, + "totp_secret": totp_secret, "values": values, }, credential_update_params.CredentialUpdateParams, @@ -230,7 +252,7 @@ def list( def delete( self, - id: str, + id_or_name: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -240,7 +262,7 @@ def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete a credential by its ID. + Delete a credential by its ID or name. Args: extra_headers: Send extra headers @@ -251,17 +273,52 @@ def delete( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/credentials/{id}", + f"/credentials/{id_or_name}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=NoneType, ) + def totp_code( + self, + id_or_name: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CredentialTotpCodeResponse: + """ + Returns the current 6-digit TOTP code for a credential with a configured + totp_secret. Use this to complete 2FA setup on sites or when you need a fresh + code. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") + return self._get( + f"/credentials/{id_or_name}/totp-code", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CredentialTotpCodeResponse, + ) + class AsyncCredentialsResource(AsyncAPIResource): @cached_property @@ -289,6 +346,8 @@ async def create( domain: str, name: str, values: Dict[str, str], + sso_provider: str | Omit = omit, + totp_secret: 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, @@ -296,10 +355,8 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Credential: - """Create a new credential for storing login information. - - Values are encrypted at - rest. + """ + Create a new credential for storing login information. Args: domain: Target domain this credential is for @@ -308,6 +365,14 @@ async def create( values: Field name to value mapping (e.g., username, password) + sso_provider: If set, indicates this credential should be used with the specified SSO provider + (e.g., google, github, microsoft). When the target site has a matching SSO + button, it will be clicked first before filling credential values on the + identity provider's login page. + + totp_secret: Base32-encoded TOTP secret for generating one-time passwords. Used for automatic + 2FA during login. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -323,6 +388,8 @@ async def create( "domain": domain, "name": name, "values": values, + "sso_provider": sso_provider, + "totp_secret": totp_secret, }, credential_create_params.CredentialCreateParams, ), @@ -334,7 +401,7 @@ async def create( async def retrieve( self, - id: str, + id_or_name: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -343,7 +410,7 @@ async def retrieve( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Credential: - """Retrieve a credential by its ID. + """Retrieve a credential by its ID or name. Credential values are not returned. @@ -356,10 +423,10 @@ async def retrieve( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return await self._get( - f"/credentials/{id}", + f"/credentials/{id_or_name}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -368,9 +435,11 @@ async def retrieve( async def update( self, - id: str, + id_or_name: str, *, name: str | Omit = omit, + sso_provider: Optional[str] | Omit = omit, + totp_secret: str | Omit = omit, values: Dict[str, str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -381,13 +450,20 @@ async def update( ) -> Credential: """Update a credential's name or values. - Values are encrypted at rest. + When values are provided, they are merged + with existing values (new keys are added, existing keys are overwritten). Args: name: New name for the credential - values: Field name to value mapping (e.g., username, password). Replaces all existing - values. + sso_provider: If set, indicates this credential should be used with the specified SSO + provider. Set to empty string or null to remove. + + totp_secret: Base32-encoded TOTP secret for generating one-time passwords. Spaces and + formatting are automatically normalized. Set to empty string to remove. + + values: Field name to value mapping. Values are merged with existing values (new keys + added, existing keys overwritten). extra_headers: Send extra headers @@ -397,13 +473,15 @@ async def update( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return await self._patch( - f"/credentials/{id}", + f"/credentials/{id_or_name}", body=await async_maybe_transform( { "name": name, + "sso_provider": sso_provider, + "totp_secret": totp_secret, "values": values, }, credential_update_params.CredentialUpdateParams, @@ -469,7 +547,7 @@ def list( async def delete( self, - id: str, + id_or_name: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -479,7 +557,7 @@ async def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete a credential by its ID. + Delete a credential by its ID or name. Args: extra_headers: Send extra headers @@ -490,17 +568,52 @@ async def delete( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/credentials/{id}", + f"/credentials/{id_or_name}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=NoneType, ) + async def totp_code( + self, + id_or_name: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CredentialTotpCodeResponse: + """ + Returns the current 6-digit TOTP code for a credential with a configured + totp_secret. Use this to complete 2FA setup on sites or when you need a fresh + code. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") + return await self._get( + f"/credentials/{id_or_name}/totp-code", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CredentialTotpCodeResponse, + ) + class CredentialsResourceWithRawResponse: def __init__(self, credentials: CredentialsResource) -> None: @@ -521,6 +634,9 @@ def __init__(self, credentials: CredentialsResource) -> None: self.delete = to_raw_response_wrapper( credentials.delete, ) + self.totp_code = to_raw_response_wrapper( + credentials.totp_code, + ) class AsyncCredentialsResourceWithRawResponse: @@ -542,6 +658,9 @@ def __init__(self, credentials: AsyncCredentialsResource) -> None: self.delete = async_to_raw_response_wrapper( credentials.delete, ) + self.totp_code = async_to_raw_response_wrapper( + credentials.totp_code, + ) class CredentialsResourceWithStreamingResponse: @@ -563,6 +682,9 @@ def __init__(self, credentials: CredentialsResource) -> None: self.delete = to_streamed_response_wrapper( credentials.delete, ) + self.totp_code = to_streamed_response_wrapper( + credentials.totp_code, + ) class AsyncCredentialsResourceWithStreamingResponse: @@ -584,3 +706,6 @@ def __init__(self, credentials: AsyncCredentialsResource) -> None: self.delete = async_to_streamed_response_wrapper( credentials.delete, ) + self.totp_code = async_to_streamed_response_wrapper( + credentials.totp_code, + ) diff --git a/src/kernel/resources/proxies.py b/src/kernel/resources/proxies.py index ba6862f..4908ab7 100644 --- a/src/kernel/resources/proxies.py +++ b/src/kernel/resources/proxies.py @@ -19,6 +19,7 @@ ) from .._base_client import make_request_options from ..types.proxy_list_response import ProxyListResponse +from ..types.proxy_check_response import ProxyCheckResponse from ..types.proxy_create_response import ProxyCreateResponse from ..types.proxy_retrieve_response import ProxyRetrieveResponse @@ -184,6 +185,39 @@ def delete( cast_to=NoneType, ) + def check( + 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, + ) -> ProxyCheckResponse: + """ + Run a health check on the proxy to verify it's working. + + 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._post( + f"/proxies/{id}/check", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProxyCheckResponse, + ) + class AsyncProxiesResource(AsyncAPIResource): @cached_property @@ -344,6 +378,39 @@ async def delete( cast_to=NoneType, ) + async def check( + 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, + ) -> ProxyCheckResponse: + """ + Run a health check on the proxy to verify it's working. + + 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._post( + f"/proxies/{id}/check", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ProxyCheckResponse, + ) + class ProxiesResourceWithRawResponse: def __init__(self, proxies: ProxiesResource) -> None: @@ -361,6 +428,9 @@ def __init__(self, proxies: ProxiesResource) -> None: self.delete = to_raw_response_wrapper( proxies.delete, ) + self.check = to_raw_response_wrapper( + proxies.check, + ) class AsyncProxiesResourceWithRawResponse: @@ -379,6 +449,9 @@ def __init__(self, proxies: AsyncProxiesResource) -> None: self.delete = async_to_raw_response_wrapper( proxies.delete, ) + self.check = async_to_raw_response_wrapper( + proxies.check, + ) class ProxiesResourceWithStreamingResponse: @@ -397,6 +470,9 @@ def __init__(self, proxies: ProxiesResource) -> None: self.delete = to_streamed_response_wrapper( proxies.delete, ) + self.check = to_streamed_response_wrapper( + proxies.check, + ) class AsyncProxiesResourceWithStreamingResponse: @@ -415,3 +491,6 @@ def __init__(self, proxies: AsyncProxiesResource) -> None: self.delete = async_to_streamed_response_wrapper( proxies.delete, ) + self.check = async_to_streamed_response_wrapper( + proxies.check, + ) diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py index 5fad216..0665e53 100644 --- a/src/kernel/types/__init__.py +++ b/src/kernel/types/__init__.py @@ -22,6 +22,7 @@ from .browser_persistence import BrowserPersistence as BrowserPersistence from .proxy_create_params import ProxyCreateParams as ProxyCreateParams from .proxy_list_response import ProxyListResponse as ProxyListResponse +from .proxy_check_response import ProxyCheckResponse as ProxyCheckResponse from .browser_create_params import BrowserCreateParams as BrowserCreateParams from .browser_delete_params import BrowserDeleteParams as BrowserDeleteParams from .browser_list_response import BrowserListResponse as BrowserListResponse @@ -63,6 +64,7 @@ from .deployment_retrieve_response import DeploymentRetrieveResponse as DeploymentRetrieveResponse from .invocation_retrieve_response import InvocationRetrieveResponse as InvocationRetrieveResponse from .browser_pool_acquire_response import BrowserPoolAcquireResponse as BrowserPoolAcquireResponse +from .credential_totp_code_response import CredentialTotpCodeResponse as CredentialTotpCodeResponse from .browser_load_extensions_params import BrowserLoadExtensionsParams as BrowserLoadExtensionsParams from .extension_download_from_chrome_store_params import ( ExtensionDownloadFromChromeStoreParams as ExtensionDownloadFromChromeStoreParams, diff --git a/src/kernel/types/agents/__init__.py b/src/kernel/types/agents/__init__.py index e2c3bb0..2ecdef6 100644 --- a/src/kernel/types/agents/__init__.py +++ b/src/kernel/types/agents/__init__.py @@ -3,12 +3,10 @@ from __future__ import annotations from .auth_agent import AuthAgent as AuthAgent -from .reauth_response import ReauthResponse as ReauthResponse from .auth_list_params import AuthListParams as AuthListParams from .discovered_field import DiscoveredField as DiscoveredField from .auth_create_params import AuthCreateParams as AuthCreateParams from .agent_auth_submit_response import AgentAuthSubmitResponse as AgentAuthSubmitResponse -from .agent_auth_discover_response import AgentAuthDiscoverResponse as AgentAuthDiscoverResponse from .agent_auth_invocation_response import AgentAuthInvocationResponse as AgentAuthInvocationResponse from .auth_agent_invocation_create_response import ( AuthAgentInvocationCreateResponse as AuthAgentInvocationCreateResponse, diff --git a/src/kernel/types/agents/agent_auth_discover_response.py b/src/kernel/types/agents/agent_auth_discover_response.py deleted file mode 100644 index 5e411dc..0000000 --- a/src/kernel/types/agents/agent_auth_discover_response.py +++ /dev/null @@ -1,30 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional - -from ..._models import BaseModel -from .discovered_field import DiscoveredField - -__all__ = ["AgentAuthDiscoverResponse"] - - -class AgentAuthDiscoverResponse(BaseModel): - """Response from discover endpoint matching AuthBlueprint schema""" - - success: bool - """Whether discovery succeeded""" - - error_message: Optional[str] = None - """Error message if discovery failed""" - - fields: Optional[List[DiscoveredField]] = None - """Discovered form fields (present when success is true)""" - - logged_in: Optional[bool] = None - """Whether user is already logged in""" - - login_url: Optional[str] = None - """URL of the discovered login page""" - - page_title: Optional[str] = None - """Title of the login page""" diff --git a/src/kernel/types/agents/agent_auth_invocation_response.py b/src/kernel/types/agents/agent_auth_invocation_response.py index 02b5ecf..42b54a4 100644 --- a/src/kernel/types/agents/agent_auth_invocation_response.py +++ b/src/kernel/types/agents/agent_auth_invocation_response.py @@ -1,11 +1,26 @@ # 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 .discovered_field import DiscoveredField -__all__ = ["AgentAuthInvocationResponse"] +__all__ = ["AgentAuthInvocationResponse", "PendingSSOButton"] + + +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 AgentAuthInvocationResponse(BaseModel): @@ -14,11 +29,47 @@ class AgentAuthInvocationResponse(BaseModel): app_name: str """App name (org name at time of invocation creation)""" + domain: str + """Domain for authentication""" + expires_at: datetime """When the handoff code expires""" - status: Literal["IN_PROGRESS", "SUCCESS", "EXPIRED", "CANCELED"] + status: Literal["IN_PROGRESS", "SUCCESS", "EXPIRED", "CANCELED", "FAILED"] """Invocation status""" - target_domain: str - """Target domain for authentication""" + step: Literal[ + "initialized", "discovering", "awaiting_input", "awaiting_external_action", "submitting", "completed", "expired" + ] + """Current step in the invocation workflow""" + + type: Literal["login", "auto_login", "reauth"] + """The invocation type: + + - login: First-time authentication + - reauth: Re-authentication for previously authenticated agents + - auto_login: Legacy type (no longer created, kept for backward compatibility) + """ + + error_message: Optional[str] = None + """Error message explaining why the invocation failed (present when status=FAILED)""" + + external_action_message: Optional[str] = None + """ + Instructions for user when external action is required (present when + step=awaiting_external_action) + """ + + live_view_url: Optional[str] = None + """Browser live view URL for debugging the invocation""" + + pending_fields: Optional[List[DiscoveredField]] = None + """Fields currently awaiting input (present when step=awaiting_input)""" + + pending_sso_buttons: Optional[List[PendingSSOButton]] = None + """SSO buttons available on the page (present when step=awaiting_input)""" + + submitted_fields: Optional[List[str]] = None + """ + Names of fields that have been submitted (present when step=submitting or later) + """ diff --git a/src/kernel/types/agents/agent_auth_submit_response.py b/src/kernel/types/agents/agent_auth_submit_response.py index 5ca9578..8cb0df1 100644 --- a/src/kernel/types/agents/agent_auth_submit_response.py +++ b/src/kernel/types/agents/agent_auth_submit_response.py @@ -1,36 +1,14 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional - from ..._models import BaseModel -from .discovered_field import DiscoveredField __all__ = ["AgentAuthSubmitResponse"] class AgentAuthSubmitResponse(BaseModel): - """Response from submit endpoint matching SubmitResult schema""" - - success: bool - """Whether submission succeeded""" - - additional_fields: Optional[List[DiscoveredField]] = None """ - Additional fields needed (e.g., OTP) - present when needs_additional_auth is - true + Response from submit endpoint - returns immediately after submission is accepted """ - app_name: Optional[str] = None - """App name (only present when logged_in is true)""" - - error_message: Optional[str] = None - """Error message if submission failed""" - - logged_in: Optional[bool] = None - """Whether user is now logged in""" - - needs_additional_auth: Optional[bool] = None - """Whether additional authentication fields are needed""" - - target_domain: Optional[str] = None - """Target domain (only present when logged_in is true)""" + accepted: bool + """Whether the submission was accepted for processing""" diff --git a/src/kernel/types/agents/auth/__init__.py b/src/kernel/types/agents/auth/__init__.py index 0296883..41e8ba8 100644 --- a/src/kernel/types/agents/auth/__init__.py +++ b/src/kernel/types/agents/auth/__init__.py @@ -4,6 +4,5 @@ from .invocation_create_params import InvocationCreateParams as InvocationCreateParams from .invocation_submit_params import InvocationSubmitParams as InvocationSubmitParams -from .invocation_discover_params import InvocationDiscoverParams as InvocationDiscoverParams from .invocation_exchange_params import InvocationExchangeParams as InvocationExchangeParams from .invocation_exchange_response import InvocationExchangeResponse as InvocationExchangeResponse diff --git a/src/kernel/types/agents/auth/invocation_discover_params.py b/src/kernel/types/agents/auth/invocation_discover_params.py deleted file mode 100644 index aa03f0c..0000000 --- a/src/kernel/types/agents/auth/invocation_discover_params.py +++ /dev/null @@ -1,16 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import TypedDict - -__all__ = ["InvocationDiscoverParams"] - - -class InvocationDiscoverParams(TypedDict, total=False): - login_url: str - """Optional login page URL. - - If provided, will override the stored login URL for this discovery invocation - and skip Phase 1 discovery. - """ diff --git a/src/kernel/types/agents/auth/invocation_submit_params.py b/src/kernel/types/agents/auth/invocation_submit_params.py index be92e7d..ad9f9c1 100644 --- a/src/kernel/types/agents/auth/invocation_submit_params.py +++ b/src/kernel/types/agents/auth/invocation_submit_params.py @@ -2,12 +2,20 @@ from __future__ import annotations -from typing import Dict -from typing_extensions import Required, TypedDict +from typing import Dict, Union +from typing_extensions import Required, TypeAlias, TypedDict -__all__ = ["InvocationSubmitParams"] +__all__ = ["InvocationSubmitParams", "Variant0", "Variant1"] -class InvocationSubmitParams(TypedDict, total=False): +class Variant0(TypedDict, total=False): field_values: Required[Dict[str, str]] """Values for the discovered login fields""" + + +class Variant1(TypedDict, total=False): + sso_button: Required[str] + """Selector of SSO button to click""" + + +InvocationSubmitParams: TypeAlias = Union[Variant0, Variant1] diff --git a/src/kernel/types/agents/auth_agent.py b/src/kernel/types/agents/auth_agent.py index 50f9149..33fc46b 100644 --- a/src/kernel/types/agents/auth_agent.py +++ b/src/kernel/types/agents/auth_agent.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime from typing_extensions import Literal @@ -26,6 +26,13 @@ class AuthAgent(BaseModel): 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 agent's authentication flow + (besides the primary domain). Useful when login pages redirect to different + domains. + """ + can_reauth: Optional[bool] = None """ Whether automatic re-authentication is possible (has credential_id, selectors, 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 da0b6f6..6027f4d 100644 --- a/src/kernel/types/agents/auth_agent_invocation_create_response.py +++ b/src/kernel/types/agents/auth_agent_invocation_create_response.py @@ -1,24 +1,15 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Union from datetime import datetime -from typing_extensions import Literal, Annotated, TypeAlias +from typing_extensions import Literal -from ..._utils import PropertyInfo from ..._models import BaseModel -__all__ = ["AuthAgentInvocationCreateResponse", "AuthAgentAlreadyAuthenticated", "AuthAgentInvocationCreated"] +__all__ = ["AuthAgentInvocationCreateResponse"] -class AuthAgentAlreadyAuthenticated(BaseModel): - """Response when the agent is already authenticated.""" - - status: Literal["already_authenticated"] - """Indicates the agent is already authenticated and no invocation was created.""" - - -class AuthAgentInvocationCreated(BaseModel): - """Response when a new invocation was created.""" +class AuthAgentInvocationCreateResponse(BaseModel): + """Response from creating an invocation. Always returns an invocation_id.""" expires_at: datetime """When the handoff code expires.""" @@ -32,10 +23,10 @@ class AuthAgentInvocationCreated(BaseModel): invocation_id: str """Unique identifier for the invocation.""" - status: Literal["invocation_created"] - """Indicates an invocation was created.""" - + type: Literal["login", "auto_login", "reauth"] + """The invocation type: -AuthAgentInvocationCreateResponse: TypeAlias = Annotated[ - Union[AuthAgentAlreadyAuthenticated, AuthAgentInvocationCreated], PropertyInfo(discriminator="status") -] + - login: First-time authentication + - reauth: Re-authentication for previously authenticated agents + - auto_login: Legacy type (no longer created, kept for backward compatibility) + """ diff --git a/src/kernel/types/agents/auth_create_params.py b/src/kernel/types/agents/auth_create_params.py index 7cf7665..b792d56 100644 --- a/src/kernel/types/agents/auth_create_params.py +++ b/src/kernel/types/agents/auth_create_params.py @@ -4,15 +4,24 @@ from typing_extensions import Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["AuthCreateParams", "Proxy"] class AuthCreateParams(TypedDict, total=False): + domain: Required[str] + """Domain for authentication""" + profile_name: Required[str] """Name of the profile to use for this auth agent""" - target_domain: Required[str] - """Target domain for authentication""" + allowed_domains: SequenceNotStr[str] + """ + Additional domains that are valid for this auth agent's authentication flow + (besides the primary domain). Useful when login pages redirect to different + domains. + """ credential_name: str """Optional name of an existing credential to use for this auth agent. diff --git a/src/kernel/types/agents/auth_list_params.py b/src/kernel/types/agents/auth_list_params.py index a4b2ffc..52d5337 100644 --- a/src/kernel/types/agents/auth_list_params.py +++ b/src/kernel/types/agents/auth_list_params.py @@ -8,6 +8,9 @@ class AuthListParams(TypedDict, total=False): + domain: str + """Filter by domain""" + limit: int """Maximum number of results to return""" @@ -16,6 +19,3 @@ class AuthListParams(TypedDict, total=False): profile_name: str """Filter by profile name""" - - target_domain: str - """Filter by target domain""" diff --git a/src/kernel/types/agents/discovered_field.py b/src/kernel/types/agents/discovered_field.py index 0c6715c..72ac294 100644 --- a/src/kernel/types/agents/discovered_field.py +++ b/src/kernel/types/agents/discovered_field.py @@ -20,7 +20,7 @@ class DiscoveredField(BaseModel): selector: str """CSS selector for the field""" - type: Literal["text", "email", "password", "tel", "number", "url", "code"] + type: Literal["text", "email", "password", "tel", "number", "url", "code", "totp"] """Field type""" placeholder: Optional[str] = None diff --git a/src/kernel/types/agents/reauth_response.py b/src/kernel/types/agents/reauth_response.py deleted file mode 100644 index 4ead46a..0000000 --- a/src/kernel/types/agents/reauth_response.py +++ /dev/null @@ -1,21 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from typing_extensions import Literal - -from ..._models import BaseModel - -__all__ = ["ReauthResponse"] - - -class ReauthResponse(BaseModel): - """Response from triggering re-authentication""" - - status: Literal["reauth_started", "already_authenticated", "cannot_reauth"] - """Result of the re-authentication attempt""" - - invocation_id: Optional[str] = None - """ID of the re-auth invocation if one was created""" - - message: Optional[str] = None - """Human-readable description of the result""" diff --git a/src/kernel/types/credential.py b/src/kernel/types/credential.py index 30def06..8ae733b 100644 --- a/src/kernel/types/credential.py +++ b/src/kernel/types/credential.py @@ -1,5 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Optional from datetime import datetime from .._models import BaseModel @@ -24,3 +25,23 @@ class Credential(BaseModel): updated_at: datetime """When the credential was last updated""" + + has_totp_secret: Optional[bool] = None + """Whether this credential has a TOTP secret configured for automatic 2FA""" + + sso_provider: Optional[str] = None + """ + If set, indicates this credential should be used with the specified SSO provider + (e.g., google, github, microsoft). When the target site has a matching SSO + button, it will be clicked first before filling credential values on the + identity provider's login page. + """ + + totp_code: Optional[str] = None + """Current 6-digit TOTP code. + + Only included in create/update responses when totp_secret was just set. + """ + + totp_code_expires_at: Optional[datetime] = None + """When the totp_code expires. Only included when totp_code is present.""" diff --git a/src/kernel/types/credential_create_params.py b/src/kernel/types/credential_create_params.py index 3f7b2d9..94964b9 100644 --- a/src/kernel/types/credential_create_params.py +++ b/src/kernel/types/credential_create_params.py @@ -17,3 +17,17 @@ class CredentialCreateParams(TypedDict, total=False): values: Required[Dict[str, str]] """Field name to value mapping (e.g., username, password)""" + + sso_provider: str + """ + If set, indicates this credential should be used with the specified SSO provider + (e.g., google, github, microsoft). When the target site has a matching SSO + button, it will be clicked first before filling credential values on the + identity provider's login page. + """ + + totp_secret: str + """Base32-encoded TOTP secret for generating one-time passwords. + + Used for automatic 2FA during login. + """ diff --git a/src/kernel/types/credential_totp_code_response.py b/src/kernel/types/credential_totp_code_response.py new file mode 100644 index 0000000..670f4e7 --- /dev/null +++ b/src/kernel/types/credential_totp_code_response.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["CredentialTotpCodeResponse"] + + +class CredentialTotpCodeResponse(BaseModel): + code: str + """Current 6-digit TOTP code""" + + expires_at: datetime + """When this code expires (ISO 8601 timestamp)""" diff --git a/src/kernel/types/credential_update_params.py b/src/kernel/types/credential_update_params.py index ffc0c1c..c42209e 100644 --- a/src/kernel/types/credential_update_params.py +++ b/src/kernel/types/credential_update_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict +from typing import Dict, Optional from typing_extensions import TypedDict __all__ = ["CredentialUpdateParams"] @@ -12,8 +12,23 @@ class CredentialUpdateParams(TypedDict, total=False): name: str """New name for the credential""" + sso_provider: Optional[str] + """If set, indicates this credential should be used with the specified SSO + provider. + + Set to empty string or null to remove. + """ + + totp_secret: str + """Base32-encoded TOTP secret for generating one-time passwords. + + Spaces and formatting are automatically normalized. Set to empty string to + remove. + """ + values: Dict[str, str] - """Field name to value mapping (e.g., username, password). + """Field name to value mapping. - Replaces all existing values. + Values are merged with existing values (new keys added, existing keys + overwritten). """ diff --git a/src/kernel/types/proxy_check_response.py b/src/kernel/types/proxy_check_response.py new file mode 100644 index 0000000..dc45f4f --- /dev/null +++ b/src/kernel/types/proxy_check_response.py @@ -0,0 +1,195 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, TypeAlias + +from .._models import BaseModel + +__all__ = [ + "ProxyCheckResponse", + "Config", + "ConfigDatacenterProxyConfig", + "ConfigIspProxyConfig", + "ConfigResidentialProxyConfig", + "ConfigMobileProxyConfig", + "ConfigCustomProxyConfig", +] + + +class ConfigDatacenterProxyConfig(BaseModel): + """Configuration for a datacenter proxy.""" + + country: Optional[str] = None + """ISO 3166 country code. Defaults to US if not provided.""" + + +class ConfigIspProxyConfig(BaseModel): + """Configuration for an ISP proxy.""" + + country: Optional[str] = None + """ISO 3166 country code. Defaults to US if not provided.""" + + +class ConfigResidentialProxyConfig(BaseModel): + """Configuration for residential proxies.""" + + asn: Optional[str] = None + """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html""" + + city: Optional[str] = None + """City name (no spaces, e.g. + + `sanfrancisco`). If provided, `country` must also be provided. + """ + + country: Optional[str] = None + """ISO 3166 country code.""" + + os: Optional[Literal["windows", "macos", "android"]] = None + """Operating system of the residential device.""" + + state: Optional[str] = None + """Two-letter state code.""" + + zip: Optional[str] = None + """US ZIP code.""" + + +class ConfigMobileProxyConfig(BaseModel): + """Configuration for mobile proxies.""" + + asn: Optional[str] = None + """Autonomous system number. See https://bgp.potaroo.net/cidr/autnums.html""" + + carrier: Optional[ + Literal[ + "a1", + "aircel", + "airtel", + "att", + "celcom", + "chinamobile", + "claro", + "comcast", + "cox", + "digi", + "dt", + "docomo", + "dtac", + "etisalat", + "idea", + "kyivstar", + "meo", + "megafon", + "mtn", + "mtnza", + "mts", + "optus", + "orange", + "qwest", + "reliance_jio", + "robi", + "sprint", + "telefonica", + "telstra", + "tmobile", + "tigo", + "tim", + "verizon", + "vimpelcom", + "vodacomza", + "vodafone", + "vivo", + "zain", + "vivabo", + "telenormyanmar", + "kcelljsc", + "swisscom", + "singtel", + "asiacell", + "windit", + "cellc", + "ooredoo", + "drei", + "umobile", + "cableone", + "proximus", + "tele2", + "mobitel", + "o2", + "bouygues", + "free", + "sfr", + "digicel", + ] + ] = None + """Mobile carrier.""" + + city: Optional[str] = None + """City name (no spaces, e.g. + + `sanfrancisco`). If provided, `country` must also be provided. + """ + + country: Optional[str] = None + """ISO 3166 country code""" + + state: Optional[str] = None + """Two-letter state code.""" + + zip: Optional[str] = None + """US ZIP code.""" + + +class ConfigCustomProxyConfig(BaseModel): + """Configuration for a custom proxy (e.g., private proxy server).""" + + host: str + """Proxy host address or IP.""" + + port: int + """Proxy port.""" + + has_password: Optional[bool] = None + """Whether the proxy has a password.""" + + username: Optional[str] = None + """Username for proxy authentication.""" + + +Config: TypeAlias = Union[ + ConfigDatacenterProxyConfig, + ConfigIspProxyConfig, + ConfigResidentialProxyConfig, + ConfigMobileProxyConfig, + ConfigCustomProxyConfig, +] + + +class ProxyCheckResponse(BaseModel): + """Configuration for routing traffic through a proxy.""" + + type: Literal["datacenter", "isp", "residential", "mobile", "custom"] + """Proxy type to use. + + In terms of quality for avoiding bot-detection, from best to worst: `mobile` > + `residential` > `isp` > `datacenter`. + """ + + id: Optional[str] = None + + config: Optional[Config] = None + """Configuration specific to the selected proxy `type`.""" + + last_checked: Optional[datetime] = None + """Timestamp of the last health check performed on this proxy.""" + + name: Optional[str] = None + """Readable name of the proxy.""" + + protocol: Optional[Literal["http", "https"]] = None + """Protocol to use for the proxy connection.""" + + status: Optional[Literal["available", "unavailable"]] = None + """Current health status of the proxy.""" diff --git a/tests/api_resources/agents/auth/test_invocations.py b/tests/api_resources/agents/auth/test_invocations.py index eef21a9..1bae66d 100644 --- a/tests/api_resources/agents/auth/test_invocations.py +++ b/tests/api_resources/agents/auth/test_invocations.py @@ -9,12 +9,7 @@ from kernel import Kernel, AsyncKernel from tests.utils import assert_matches_type -from kernel.types.agents import ( - AgentAuthSubmitResponse, - AgentAuthDiscoverResponse, - AgentAuthInvocationResponse, - AuthAgentInvocationCreateResponse, -) +from kernel.types.agents import AgentAuthSubmitResponse, AgentAuthInvocationResponse, AuthAgentInvocationCreateResponse from kernel.types.agents.auth import ( InvocationExchangeResponse, ) @@ -110,57 +105,6 @@ def test_path_params_retrieve(self, client: Kernel) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_discover(self, client: Kernel) -> None: - invocation = client.agents.auth.invocations.discover( - invocation_id="invocation_id", - ) - assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_discover_with_all_params(self, client: Kernel) -> None: - invocation = client.agents.auth.invocations.discover( - invocation_id="invocation_id", - login_url="https://doordash.com/account/login", - ) - assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_raw_response_discover(self, client: Kernel) -> None: - response = client.agents.auth.invocations.with_raw_response.discover( - invocation_id="invocation_id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - invocation = response.parse() - assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_streaming_response_discover(self, client: Kernel) -> None: - with client.agents.auth.invocations.with_streaming_response.discover( - invocation_id="invocation_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - invocation = response.parse() - assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_path_params_discover(self, client: Kernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `invocation_id` but received ''"): - client.agents.auth.invocations.with_raw_response.discover( - invocation_id="", - ) - @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_exchange(self, client: Kernel) -> None: @@ -209,7 +153,7 @@ def test_path_params_exchange(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_method_submit(self, client: Kernel) -> None: + def test_method_submit_overload_1(self, client: Kernel) -> None: invocation = client.agents.auth.invocations.submit( invocation_id="invocation_id", field_values={ @@ -221,7 +165,7 @@ def test_method_submit(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_raw_response_submit(self, client: Kernel) -> None: + 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={ @@ -237,7 +181,7 @@ def test_raw_response_submit(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_streaming_response_submit(self, client: Kernel) -> None: + 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={ @@ -255,7 +199,7 @@ def test_streaming_response_submit(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_path_params_submit(self, client: Kernel) -> None: + 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="", @@ -265,6 +209,52 @@ def test_path_params_submit(self, client: Kernel) -> None: }, ) + @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')]", + ) + 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')]", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = response.parse() + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_submit_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" + + 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')]", + ) + class TestAsyncInvocations: parametrize = pytest.mark.parametrize( @@ -356,57 +346,6 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_discover(self, async_client: AsyncKernel) -> None: - invocation = await async_client.agents.auth.invocations.discover( - invocation_id="invocation_id", - ) - assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_discover_with_all_params(self, async_client: AsyncKernel) -> None: - invocation = await async_client.agents.auth.invocations.discover( - invocation_id="invocation_id", - login_url="https://doordash.com/account/login", - ) - assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_raw_response_discover(self, async_client: AsyncKernel) -> None: - response = await async_client.agents.auth.invocations.with_raw_response.discover( - invocation_id="invocation_id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - invocation = await response.parse() - assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_streaming_response_discover(self, async_client: AsyncKernel) -> None: - async with async_client.agents.auth.invocations.with_streaming_response.discover( - invocation_id="invocation_id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - invocation = await response.parse() - assert_matches_type(AgentAuthDiscoverResponse, invocation, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_path_params_discover(self, async_client: AsyncKernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `invocation_id` but received ''"): - await async_client.agents.auth.invocations.with_raw_response.discover( - invocation_id="", - ) - @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_exchange(self, async_client: AsyncKernel) -> None: @@ -455,7 +394,7 @@ async def test_path_params_exchange(self, async_client: AsyncKernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_method_submit(self, async_client: AsyncKernel) -> None: + 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={ @@ -467,7 +406,7 @@ async def test_method_submit(self, async_client: AsyncKernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_raw_response_submit(self, async_client: AsyncKernel) -> None: + 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={ @@ -483,7 +422,7 @@ async def test_raw_response_submit(self, async_client: AsyncKernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_streaming_response_submit(self, async_client: AsyncKernel) -> None: + 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={ @@ -501,7 +440,7 @@ async def test_streaming_response_submit(self, async_client: AsyncKernel) -> Non @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_path_params_submit(self, async_client: AsyncKernel) -> None: + 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="", @@ -510,3 +449,49 @@ async def test_path_params_submit(self, async_client: AsyncKernel) -> None: "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')]", + ) + 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')]", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + invocation = await response.parse() + assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_submit_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" + + 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')]", + ) diff --git a/tests/api_resources/agents/test_auth.py b/tests/api_resources/agents/test_auth.py index 192361a..9855ef8 100644 --- a/tests/api_resources/agents/test_auth.py +++ b/tests/api_resources/agents/test_auth.py @@ -10,7 +10,7 @@ from kernel import Kernel, AsyncKernel from tests.utils import assert_matches_type from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination -from kernel.types.agents import AuthAgent, ReauthResponse +from kernel.types.agents import AuthAgent base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,8 +22,8 @@ class TestAuth: @parametrize def test_method_create(self, client: Kernel) -> None: auth = client.agents.auth.create( + domain="netflix.com", profile_name="user-123", - target_domain="netflix.com", ) assert_matches_type(AuthAgent, auth, path=["response"]) @@ -31,8 +31,9 @@ def test_method_create(self, client: Kernel) -> None: @parametrize def test_method_create_with_all_params(self, client: Kernel) -> None: auth = client.agents.auth.create( + domain="netflix.com", profile_name="user-123", - target_domain="netflix.com", + allowed_domains=["login.netflix.com", "auth.netflix.com"], credential_name="my-netflix-login", login_url="https://netflix.com/login", proxy={"proxy_id": "proxy_id"}, @@ -43,8 +44,8 @@ def test_method_create_with_all_params(self, client: Kernel) -> None: @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", - target_domain="netflix.com", ) assert response.is_closed is True @@ -56,8 +57,8 @@ def test_raw_response_create(self, client: Kernel) -> None: @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", - target_domain="netflix.com", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -119,10 +120,10 @@ def test_method_list(self, client: Kernel) -> None: @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", - target_domain="target_domain", ) assert_matches_type(SyncOffsetPagination[AuthAgent], auth, path=["response"]) @@ -190,48 +191,6 @@ def test_path_params_delete(self, client: Kernel) -> None: "", ) - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_reauth(self, client: Kernel) -> None: - auth = client.agents.auth.reauth( - "id", - ) - assert_matches_type(ReauthResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_raw_response_reauth(self, client: Kernel) -> None: - response = client.agents.auth.with_raw_response.reauth( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth = response.parse() - assert_matches_type(ReauthResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_streaming_response_reauth(self, client: Kernel) -> None: - with client.agents.auth.with_streaming_response.reauth( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - auth = response.parse() - assert_matches_type(ReauthResponse, auth, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_path_params_reauth(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.reauth( - "", - ) - class TestAsyncAuth: parametrize = pytest.mark.parametrize( @@ -242,8 +201,8 @@ class TestAsyncAuth: @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", - target_domain="netflix.com", ) assert_matches_type(AuthAgent, auth, path=["response"]) @@ -251,8 +210,9 @@ async def test_method_create(self, async_client: AsyncKernel) -> None: @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", - target_domain="netflix.com", + allowed_domains=["login.netflix.com", "auth.netflix.com"], credential_name="my-netflix-login", login_url="https://netflix.com/login", proxy={"proxy_id": "proxy_id"}, @@ -263,8 +223,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> @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", - target_domain="netflix.com", ) assert response.is_closed is True @@ -276,8 +236,8 @@ async def test_raw_response_create(self, async_client: AsyncKernel) -> None: @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", - target_domain="netflix.com", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -339,10 +299,10 @@ async def test_method_list(self, async_client: AsyncKernel) -> None: @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", - target_domain="target_domain", ) assert_matches_type(AsyncOffsetPagination[AuthAgent], auth, path=["response"]) @@ -409,45 +369,3 @@ async def test_path_params_delete(self, async_client: AsyncKernel) -> None: await async_client.agents.auth.with_raw_response.delete( "", ) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_reauth(self, async_client: AsyncKernel) -> None: - auth = await async_client.agents.auth.reauth( - "id", - ) - assert_matches_type(ReauthResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_raw_response_reauth(self, async_client: AsyncKernel) -> None: - response = await async_client.agents.auth.with_raw_response.reauth( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - auth = await response.parse() - assert_matches_type(ReauthResponse, auth, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_streaming_response_reauth(self, async_client: AsyncKernel) -> None: - async with async_client.agents.auth.with_streaming_response.reauth( - "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(ReauthResponse, 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_reauth(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.reauth( - "", - ) diff --git a/tests/api_resources/test_credentials.py b/tests/api_resources/test_credentials.py index 00c7635..b609868 100644 --- a/tests/api_resources/test_credentials.py +++ b/tests/api_resources/test_credentials.py @@ -9,7 +9,10 @@ from kernel import Kernel, AsyncKernel from tests.utils import assert_matches_type -from kernel.types import Credential +from kernel.types import ( + Credential, + CredentialTotpCodeResponse, +) from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -31,6 +34,21 @@ def test_method_create(self, client: Kernel) -> None: ) assert_matches_type(Credential, credential, path=["response"]) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Kernel) -> None: + credential = client.credentials.create( + domain="netflix.com", + name="my-netflix-login", + values={ + "username": "user@example.com", + "password": "mysecretpassword", + }, + sso_provider="google", + totp_secret="JBSWY3DPEHPK3PXP", + ) + assert_matches_type(Credential, credential, path=["response"]) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Kernel) -> None: @@ -71,7 +89,7 @@ def test_streaming_response_create(self, client: Kernel) -> None: @parametrize def test_method_retrieve(self, client: Kernel) -> None: credential = client.credentials.retrieve( - "id", + "id_or_name", ) assert_matches_type(Credential, credential, path=["response"]) @@ -79,7 +97,7 @@ def test_method_retrieve(self, client: Kernel) -> None: @parametrize def test_raw_response_retrieve(self, client: Kernel) -> None: response = client.credentials.with_raw_response.retrieve( - "id", + "id_or_name", ) assert response.is_closed is True @@ -91,7 +109,7 @@ def test_raw_response_retrieve(self, client: Kernel) -> None: @parametrize def test_streaming_response_retrieve(self, client: Kernel) -> None: with client.credentials.with_streaming_response.retrieve( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -104,7 +122,7 @@ def test_streaming_response_retrieve(self, client: Kernel) -> None: @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): client.credentials.with_raw_response.retrieve( "", ) @@ -113,7 +131,7 @@ def test_path_params_retrieve(self, client: Kernel) -> None: @parametrize def test_method_update(self, client: Kernel) -> None: credential = client.credentials.update( - id="id", + id_or_name="id_or_name", ) assert_matches_type(Credential, credential, path=["response"]) @@ -121,8 +139,10 @@ def test_method_update(self, client: Kernel) -> None: @parametrize def test_method_update_with_all_params(self, client: Kernel) -> None: credential = client.credentials.update( - id="id", + id_or_name="id_or_name", name="my-updated-login", + sso_provider="google", + totp_secret="JBSWY3DPEHPK3PXP", values={ "username": "user@example.com", "password": "newpassword", @@ -134,7 +154,7 @@ def test_method_update_with_all_params(self, client: Kernel) -> None: @parametrize def test_raw_response_update(self, client: Kernel) -> None: response = client.credentials.with_raw_response.update( - id="id", + id_or_name="id_or_name", ) assert response.is_closed is True @@ -146,7 +166,7 @@ def test_raw_response_update(self, client: Kernel) -> None: @parametrize def test_streaming_response_update(self, client: Kernel) -> None: with client.credentials.with_streaming_response.update( - id="id", + id_or_name="id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -159,9 +179,9 @@ def test_streaming_response_update(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_update(self, client: Kernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): client.credentials.with_raw_response.update( - id="", + id_or_name="", ) @pytest.mark.skip(reason="Prism tests are disabled") @@ -206,7 +226,7 @@ def test_streaming_response_list(self, client: Kernel) -> None: @parametrize def test_method_delete(self, client: Kernel) -> None: credential = client.credentials.delete( - "id", + "id_or_name", ) assert credential is None @@ -214,7 +234,7 @@ def test_method_delete(self, client: Kernel) -> None: @parametrize def test_raw_response_delete(self, client: Kernel) -> None: response = client.credentials.with_raw_response.delete( - "id", + "id_or_name", ) assert response.is_closed is True @@ -226,7 +246,7 @@ def test_raw_response_delete(self, client: Kernel) -> None: @parametrize def test_streaming_response_delete(self, client: Kernel) -> None: with client.credentials.with_streaming_response.delete( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -239,11 +259,53 @@ def test_streaming_response_delete(self, client: Kernel) -> None: @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): client.credentials.with_raw_response.delete( "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_totp_code(self, client: Kernel) -> None: + credential = client.credentials.totp_code( + "id_or_name", + ) + assert_matches_type(CredentialTotpCodeResponse, credential, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_totp_code(self, client: Kernel) -> None: + response = client.credentials.with_raw_response.totp_code( + "id_or_name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = response.parse() + assert_matches_type(CredentialTotpCodeResponse, credential, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_totp_code(self, client: Kernel) -> None: + with client.credentials.with_streaming_response.totp_code( + "id_or_name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = response.parse() + assert_matches_type(CredentialTotpCodeResponse, credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_totp_code(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): + client.credentials.with_raw_response.totp_code( + "", + ) + class TestAsyncCredentials: parametrize = pytest.mark.parametrize( @@ -263,6 +325,21 @@ async def test_method_create(self, async_client: AsyncKernel) -> None: ) assert_matches_type(Credential, credential, path=["response"]) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None: + credential = await async_client.credentials.create( + domain="netflix.com", + name="my-netflix-login", + values={ + "username": "user@example.com", + "password": "mysecretpassword", + }, + sso_provider="google", + totp_secret="JBSWY3DPEHPK3PXP", + ) + assert_matches_type(Credential, credential, path=["response"]) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncKernel) -> None: @@ -303,7 +380,7 @@ async def test_streaming_response_create(self, async_client: AsyncKernel) -> Non @parametrize async def test_method_retrieve(self, async_client: AsyncKernel) -> None: credential = await async_client.credentials.retrieve( - "id", + "id_or_name", ) assert_matches_type(Credential, credential, path=["response"]) @@ -311,7 +388,7 @@ async def test_method_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: response = await async_client.credentials.with_raw_response.retrieve( - "id", + "id_or_name", ) assert response.is_closed is True @@ -323,7 +400,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> None: async with async_client.credentials.with_streaming_response.retrieve( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -336,7 +413,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> N @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): await async_client.credentials.with_raw_response.retrieve( "", ) @@ -345,7 +422,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_update(self, async_client: AsyncKernel) -> None: credential = await async_client.credentials.update( - id="id", + id_or_name="id_or_name", ) assert_matches_type(Credential, credential, path=["response"]) @@ -353,8 +430,10 @@ async def test_method_update(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None: credential = await async_client.credentials.update( - id="id", + id_or_name="id_or_name", name="my-updated-login", + sso_provider="google", + totp_secret="JBSWY3DPEHPK3PXP", values={ "username": "user@example.com", "password": "newpassword", @@ -366,7 +445,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> @parametrize async def test_raw_response_update(self, async_client: AsyncKernel) -> None: response = await async_client.credentials.with_raw_response.update( - id="id", + id_or_name="id_or_name", ) assert response.is_closed is True @@ -378,7 +457,7 @@ async def test_raw_response_update(self, async_client: AsyncKernel) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncKernel) -> None: async with async_client.credentials.with_streaming_response.update( - id="id", + id_or_name="id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -391,9 +470,9 @@ async def test_streaming_response_update(self, async_client: AsyncKernel) -> Non @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_update(self, async_client: AsyncKernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): await async_client.credentials.with_raw_response.update( - id="", + id_or_name="", ) @pytest.mark.skip(reason="Prism tests are disabled") @@ -438,7 +517,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_delete(self, async_client: AsyncKernel) -> None: credential = await async_client.credentials.delete( - "id", + "id_or_name", ) assert credential is None @@ -446,7 +525,7 @@ async def test_method_delete(self, async_client: AsyncKernel) -> None: @parametrize async def test_raw_response_delete(self, async_client: AsyncKernel) -> None: response = await async_client.credentials.with_raw_response.delete( - "id", + "id_or_name", ) assert response.is_closed is True @@ -458,7 +537,7 @@ async def test_raw_response_delete(self, async_client: AsyncKernel) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncKernel) -> None: async with async_client.credentials.with_streaming_response.delete( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -471,7 +550,49 @@ async def test_streaming_response_delete(self, async_client: AsyncKernel) -> Non @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 ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): await async_client.credentials.with_raw_response.delete( "", ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_totp_code(self, async_client: AsyncKernel) -> None: + credential = await async_client.credentials.totp_code( + "id_or_name", + ) + assert_matches_type(CredentialTotpCodeResponse, credential, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_totp_code(self, async_client: AsyncKernel) -> None: + response = await async_client.credentials.with_raw_response.totp_code( + "id_or_name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = await response.parse() + assert_matches_type(CredentialTotpCodeResponse, credential, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_totp_code(self, async_client: AsyncKernel) -> None: + async with async_client.credentials.with_streaming_response.totp_code( + "id_or_name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = await response.parse() + assert_matches_type(CredentialTotpCodeResponse, credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_totp_code(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): + await async_client.credentials.with_raw_response.totp_code( + "", + ) diff --git a/tests/api_resources/test_proxies.py b/tests/api_resources/test_proxies.py index 484848f..ed858e8 100644 --- a/tests/api_resources/test_proxies.py +++ b/tests/api_resources/test_proxies.py @@ -9,7 +9,12 @@ from kernel import Kernel, AsyncKernel from tests.utils import assert_matches_type -from kernel.types import ProxyListResponse, ProxyCreateResponse, ProxyRetrieveResponse +from kernel.types import ( + ProxyListResponse, + ProxyCheckResponse, + ProxyCreateResponse, + ProxyRetrieveResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -174,6 +179,48 @@ def test_path_params_delete(self, client: Kernel) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_check(self, client: Kernel) -> None: + proxy = client.proxies.check( + "id", + ) + assert_matches_type(ProxyCheckResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_check(self, client: Kernel) -> None: + response = client.proxies.with_raw_response.check( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + proxy = response.parse() + assert_matches_type(ProxyCheckResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_check(self, client: Kernel) -> None: + with client.proxies.with_streaming_response.check( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + proxy = response.parse() + assert_matches_type(ProxyCheckResponse, proxy, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_check(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.proxies.with_raw_response.check( + "", + ) + class TestAsyncProxies: parametrize = pytest.mark.parametrize( @@ -336,3 +383,45 @@ async def test_path_params_delete(self, async_client: AsyncKernel) -> None: await async_client.proxies.with_raw_response.delete( "", ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_check(self, async_client: AsyncKernel) -> None: + proxy = await async_client.proxies.check( + "id", + ) + assert_matches_type(ProxyCheckResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_check(self, async_client: AsyncKernel) -> None: + response = await async_client.proxies.with_raw_response.check( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + proxy = await response.parse() + assert_matches_type(ProxyCheckResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_check(self, async_client: AsyncKernel) -> None: + async with async_client.proxies.with_streaming_response.check( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + proxy = await response.parse() + assert_matches_type(ProxyCheckResponse, proxy, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_check(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.proxies.with_raw_response.check( + "", + )