From b1039911a4b2b47df0087821aae2cb0ddc374de2 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 18 Dec 2025 04:31:03 +0000
Subject: [PATCH 1/7] fix: use async_to_httpx_files in patch method
---
src/kernel/_base_client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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)
From 73e0e4621186edb18b05514b14a638d62a0e5d14 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 19 Dec 2025 04:09:53 +0000
Subject: [PATCH 2/7] chore(internal): add `--fix` argument to lint script
---
scripts/lint | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
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'
From 99b80574482322b22a5bd1ce79762471330f4945 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 20 Dec 2025 00:26:34 +0000
Subject: [PATCH 3/7] feat: Enhance AuthAgentInvocation with step and last
activity tracking
---
.stats.yml | 4 ++--
.../resources/agents/auth/invocations.py | 24 +++++++++----------
.../agents/agent_auth_invocation_response.py | 3 +++
.../auth_agent_invocation_create_response.py | 4 ++--
src/kernel/types/agents/reauth_response.py | 2 +-
5 files changed, 20 insertions(+), 17 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 99bb6c4..91fb407 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
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-2cf104c4b88159c6a50713b019188f83983748b9cacec598089cf9068dc5b1cd.yml
+openapi_spec_hash: 84ea30ae65ad7ebcc04d2f3907d1e73b
config_hash: 179f33af31ece83563163d5b3d751d13
diff --git a/src/kernel/resources/agents/auth/invocations.py b/src/kernel/resources/agents/auth/invocations.py
index ac2bb8e..f5e6053 100644
--- a/src/kernel/resources/agents/auth/invocations.py
+++ b/src/kernel/resources/agents/auth/invocations.py
@@ -116,10 +116,9 @@ 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.
-
- Uses the JWT
- returned by the exchange endpoint, or standard API key or JWT authentication.
+ """
+ Returns invocation details including status, app_name, and target_domain.
+ Supports both API key and JWT (from exchange endpoint) authentication.
Args:
extra_headers: Send extra headers
@@ -155,7 +154,7 @@ def discover(
"""
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.
+ Supports both API key and JWT (from exchange endpoint) authentication.
Args:
login_url: Optional login page URL. If provided, will override the stored login URL for
@@ -233,7 +232,8 @@ def submit(
) -> 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.
+ auth fields or success. Supports both API key and JWT (from exchange endpoint)
+ authentication.
Args:
field_values: Values for the discovered login fields
@@ -342,10 +342,9 @@ 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.
-
- Uses the JWT
- returned by the exchange endpoint, or standard API key or JWT authentication.
+ """
+ Returns invocation details including status, app_name, and target_domain.
+ Supports both API key and JWT (from exchange endpoint) authentication.
Args:
extra_headers: Send extra headers
@@ -381,7 +380,7 @@ async def discover(
"""
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.
+ Supports both API key and JWT (from exchange endpoint) authentication.
Args:
login_url: Optional login page URL. If provided, will override the stored login URL for
@@ -461,7 +460,8 @@ async def submit(
) -> 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.
+ auth fields or success. Supports both API key and JWT (from exchange endpoint)
+ authentication.
Args:
field_values: Values for the discovered login fields
diff --git a/src/kernel/types/agents/agent_auth_invocation_response.py b/src/kernel/types/agents/agent_auth_invocation_response.py
index 02b5ecf..3ddfe8e 100644
--- a/src/kernel/types/agents/agent_auth_invocation_response.py
+++ b/src/kernel/types/agents/agent_auth_invocation_response.py
@@ -20,5 +20,8 @@ class AgentAuthInvocationResponse(BaseModel):
status: Literal["IN_PROGRESS", "SUCCESS", "EXPIRED", "CANCELED"]
"""Invocation status"""
+ step: Literal["initialized", "discovering", "awaiting_input", "submitting", "completed", "expired"]
+ """Current step in the invocation workflow"""
+
target_domain: str
"""Target domain for authentication"""
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..b2a5e20 100644
--- a/src/kernel/types/agents/auth_agent_invocation_create_response.py
+++ b/src/kernel/types/agents/auth_agent_invocation_create_response.py
@@ -13,7 +13,7 @@
class AuthAgentAlreadyAuthenticated(BaseModel):
"""Response when the agent is already authenticated."""
- status: Literal["already_authenticated"]
+ status: Literal["ALREADY_AUTHENTICATED"]
"""Indicates the agent is already authenticated and no invocation was created."""
@@ -32,7 +32,7 @@ class AuthAgentInvocationCreated(BaseModel):
invocation_id: str
"""Unique identifier for the invocation."""
- status: Literal["invocation_created"]
+ status: Literal["INVOCATION_CREATED"]
"""Indicates an invocation was created."""
diff --git a/src/kernel/types/agents/reauth_response.py b/src/kernel/types/agents/reauth_response.py
index 4ead46a..4fbf0e4 100644
--- a/src/kernel/types/agents/reauth_response.py
+++ b/src/kernel/types/agents/reauth_response.py
@@ -11,7 +11,7 @@
class ReauthResponse(BaseModel):
"""Response from triggering re-authentication"""
- status: Literal["reauth_started", "already_authenticated", "cannot_reauth"]
+ status: Literal["REAUTH_STARTED", "ALREADY_AUTHENTICATED", "CANNOT_REAUTH"]
"""Result of the re-authentication attempt"""
invocation_id: Optional[str] = None
From 105801ca8028df379b2f79a8a5bd4c4e9f43c3cc Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 30 Dec 2025 21:51:16 +0000
Subject: [PATCH 4/7] feat(api): add health check endpoint for proxies
---
.stats.yml | 8 +-
api.md | 8 +-
src/kernel/resources/proxies.py | 79 +++++++++
src/kernel/types/__init__.py | 1 +
src/kernel/types/proxy_check_response.py | 195 +++++++++++++++++++++++
tests/api_resources/test_proxies.py | 91 ++++++++++-
6 files changed, 376 insertions(+), 6 deletions(-)
create mode 100644 src/kernel/types/proxy_check_response.py
diff --git a/.stats.yml b/.stats.yml
index 91fb407..579e071 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-2cf104c4b88159c6a50713b019188f83983748b9cacec598089cf9068dc5b1cd.yml
-openapi_spec_hash: 84ea30ae65ad7ebcc04d2f3907d1e73b
-config_hash: 179f33af31ece83563163d5b3d751d13
+configured_endpoints: 90
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-20fac779e9e13dc9421e467be31dbf274c39072ba0c01528ba451b48698d43c1.yml
+openapi_spec_hash: c3fc5784297ccc8f729326b62000d1f0
+config_hash: e47e015528251ee83e30367dbbb51044
diff --git a/api.md b/api.md
index fb67de5..848c104 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
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..1748bf2 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
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/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(
+ "",
+ )
From 6f18d40b0f1f98b0f50ffb64b7fe858aaefb872f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 6 Jan 2026 03:49:42 +0000
Subject: [PATCH 5/7] chore(internal): codegen related update
---
LICENSE | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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.
From e941d0fb0a62cb8a1aad2424577c825bd6764df4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 7 Jan 2026 04:53:17 +0000
Subject: [PATCH 6/7] feat(auth): add auto_login credential flow
---
.stats.yml | 8 +-
api.md | 18 +-
src/kernel/resources/agents/auth/auth.py | 131 ++-------
.../resources/agents/auth/invocations.py | 273 +++++++++---------
src/kernel/resources/credentials.py | 211 +++++++++++---
src/kernel/types/__init__.py | 1 +
src/kernel/types/agents/__init__.py | 2 -
.../agents/agent_auth_discover_response.py | 30 --
.../agents/agent_auth_invocation_response.py | 58 +++-
.../agents/agent_auth_submit_response.py | 28 +-
src/kernel/types/agents/auth/__init__.py | 1 -
.../agents/auth/invocation_discover_params.py | 16 -
.../agents/auth/invocation_submit_params.py | 16 +-
src/kernel/types/agents/auth_agent.py | 9 +-
.../auth_agent_invocation_create_response.py | 29 +-
src/kernel/types/agents/auth_create_params.py | 13 +-
src/kernel/types/agents/auth_list_params.py | 6 +-
src/kernel/types/agents/discovered_field.py | 2 +-
src/kernel/types/agents/reauth_response.py | 21 --
src/kernel/types/credential.py | 21 ++
src/kernel/types/credential_create_params.py | 14 +
.../types/credential_totp_code_response.py | 15 +
src/kernel/types/credential_update_params.py | 21 +-
.../agents/auth/test_invocations.py | 217 +++++++-------
tests/api_resources/agents/test_auth.py | 108 +------
tests/api_resources/test_credentials.py | 179 ++++++++++--
26 files changed, 783 insertions(+), 665 deletions(-)
delete mode 100644 src/kernel/types/agents/agent_auth_discover_response.py
delete mode 100644 src/kernel/types/agents/auth/invocation_discover_params.py
delete mode 100644 src/kernel/types/agents/reauth_response.py
create mode 100644 src/kernel/types/credential_totp_code_response.py
diff --git a/.stats.yml b/.stats.yml
index 579e071..434275e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 90
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-20fac779e9e13dc9421e467be31dbf274c39072ba0c01528ba451b48698d43c1.yml
-openapi_spec_hash: c3fc5784297ccc8f729326b62000d1f0
-config_hash: e47e015528251ee83e30367dbbb51044
+configured_endpoints: 89
+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/api.md b/api.md
index 848c104..db5f2df 100644
--- a/api.md
+++ b/api.md
@@ -287,7 +287,6 @@ Types:
```python
from kernel.types.agents import (
- AgentAuthDiscoverResponse,
AgentAuthInvocationResponse,
AgentAuthSubmitResponse,
AuthAgent,
@@ -295,7 +294,6 @@ from kernel.types.agents import (
AuthAgentInvocationCreateRequest,
AuthAgentInvocationCreateResponse,
DiscoveredField,
- ReauthResponse,
)
```
@@ -305,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
@@ -319,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
@@ -328,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/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 f5e6053..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,9 +106,10 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AgentAuthInvocationResponse:
- """
- Returns invocation details including status, app_name, and target_domain.
- Supports both API key and JWT (from exchange endpoint) authentication.
+ """Returns invocation details including status, app_name, and domain.
+
+ Supports both
+ API key and JWT (from exchange endpoint) authentication.
Args:
extra_headers: Send extra headers
@@ -139,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.
- Supports both API key and JWT (from exchange endpoint) authentication.
+ ) -> 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
@@ -171,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
@@ -207,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,
@@ -230,13 +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. Supports both API key and JWT (from exchange endpoint)
- authentication.
+ """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,9 +344,10 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AgentAuthInvocationResponse:
- """
- Returns invocation details including status, app_name, and target_domain.
- Supports both API key and JWT (from exchange endpoint) authentication.
+ """Returns invocation details including status, app_name, and domain.
+
+ Supports both
+ API key and JWT (from exchange endpoint) authentication.
Args:
extra_headers: Send extra headers
@@ -365,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.
- Supports both API key and JWT (from exchange endpoint) authentication.
+ ) -> 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
@@ -397,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
@@ -435,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,
@@ -458,13 +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. Supports both API key and JWT (from exchange endpoint)
- authentication.
+ """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/types/__init__.py b/src/kernel/types/__init__.py
index 1748bf2..0665e53 100644
--- a/src/kernel/types/__init__.py
+++ b/src/kernel/types/__init__.py
@@ -64,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 3ddfe8e..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,14 +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"""
- step: Literal["initialized", "discovering", "awaiting_input", "submitting", "completed", "expired"]
+ step: Literal[
+ "initialized", "discovering", "awaiting_input", "awaiting_external_action", "submitting", "completed", "expired"
+ ]
"""Current step in the invocation workflow"""
- target_domain: str
- """Target domain for authentication"""
+ 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 b2a5e20..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 4fbf0e4..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/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(
+ "",
+ )
From 30051b8ca35c2f997544a7ac11523db142d3ef16 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 7 Jan 2026 04:53:36 +0000
Subject: [PATCH 7/7] release: 0.25.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 21 +++++++++++++++++++++
pyproject.toml | 2 +-
src/kernel/_version.py | 2 +-
4 files changed, 24 insertions(+), 3 deletions(-)
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/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/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/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