Skip to content

Commit b6ee08d

Browse files
feat: add MFA options to agent authentication workflow
1 parent cc37af5 commit b6ee08d

File tree

8 files changed

+201
-14
lines changed

8 files changed

+201
-14
lines changed

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 89
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-8d66dbedea5b240936b338809f272568ca84a452fc13dbda835479f2ec068b41.yml
3-
openapi_spec_hash: 7c499bfce2e996f1fff5e7791cea390e
4-
config_hash: 2ee8c7057fa9b05cd0dabd23247c40ec
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-8e4a29d23d2882fcb0864606091790fd58bffa4f5d5c8d081052b72ad47b215b.yml
3+
openapi_spec_hash: fc82d930dad739ac01e3c2bddba7bf61
4+
config_hash: 3a5e36dfb245210cfd978f679b3641d2

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ It is generated with [Stainless](https://www.stainless.com/).
1111

1212
## Documentation
1313

14-
The REST API documentation can be found on [docs.onkernel.com](https://docs.onkernel.com). The full API of this library can be found in [api.md](api.md).
14+
The REST API documentation can be found on [docs.kernel.com](https://docs.kernel.com). The full API of this library can be found in [api.md](api.md).
1515

1616
## Installation
1717

src/kernel/_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
]
6868

6969
ENVIRONMENTS: Dict[str, str] = {
70-
"production": "https://api.onkernel.com/",
70+
"production": "https://api.kernel.com/",
7171
"development": "https://localhost:3001/",
7272
}
7373

src/kernel/resources/agents/auth/invocations.py

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from typing import Dict
6-
from typing_extensions import overload
6+
from typing_extensions import Literal, overload
77

88
import httpx
99

@@ -233,13 +233,46 @@ def submit(
233233
"""
234234
...
235235

236-
@required_args(["field_values"], ["sso_button"])
236+
@overload
237+
def submit(
238+
self,
239+
invocation_id: str,
240+
*,
241+
selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "security_key"],
242+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
243+
# The extra values given here take precedence over values defined on the client or passed to this method.
244+
extra_headers: Headers | None = None,
245+
extra_query: Query | None = None,
246+
extra_body: Body | None = None,
247+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
248+
) -> AgentAuthSubmitResponse:
249+
"""Submits field values for the discovered login form.
250+
251+
Returns immediately after
252+
submission is accepted. Poll the invocation endpoint to track progress and get
253+
results.
254+
255+
Args:
256+
selected_mfa_type: The MFA delivery method type
257+
258+
extra_headers: Send extra headers
259+
260+
extra_query: Add additional query parameters to the request
261+
262+
extra_body: Add additional JSON properties to the request
263+
264+
timeout: Override the client-level default timeout for this request, in seconds
265+
"""
266+
...
267+
268+
@required_args(["field_values"], ["sso_button"], ["selected_mfa_type"])
237269
def submit(
238270
self,
239271
invocation_id: str,
240272
*,
241273
field_values: Dict[str, str] | Omit = omit,
242274
sso_button: str | Omit = omit,
275+
selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "security_key"] | Omit = omit,
243276
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
244277
# The extra values given here take precedence over values defined on the client or passed to this method.
245278
extra_headers: Headers | None = None,
@@ -255,6 +288,7 @@ def submit(
255288
{
256289
"field_values": field_values,
257290
"sso_button": sso_button,
291+
"selected_mfa_type": selected_mfa_type,
258292
},
259293
invocation_submit_params.InvocationSubmitParams,
260294
),
@@ -471,13 +505,46 @@ async def submit(
471505
"""
472506
...
473507

474-
@required_args(["field_values"], ["sso_button"])
508+
@overload
509+
async def submit(
510+
self,
511+
invocation_id: str,
512+
*,
513+
selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "security_key"],
514+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
515+
# The extra values given here take precedence over values defined on the client or passed to this method.
516+
extra_headers: Headers | None = None,
517+
extra_query: Query | None = None,
518+
extra_body: Body | None = None,
519+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
520+
) -> AgentAuthSubmitResponse:
521+
"""Submits field values for the discovered login form.
522+
523+
Returns immediately after
524+
submission is accepted. Poll the invocation endpoint to track progress and get
525+
results.
526+
527+
Args:
528+
selected_mfa_type: The MFA delivery method type
529+
530+
extra_headers: Send extra headers
531+
532+
extra_query: Add additional query parameters to the request
533+
534+
extra_body: Add additional JSON properties to the request
535+
536+
timeout: Override the client-level default timeout for this request, in seconds
537+
"""
538+
...
539+
540+
@required_args(["field_values"], ["sso_button"], ["selected_mfa_type"])
475541
async def submit(
476542
self,
477543
invocation_id: str,
478544
*,
479545
field_values: Dict[str, str] | Omit = omit,
480546
sso_button: str | Omit = omit,
547+
selected_mfa_type: Literal["sms", "call", "email", "totp", "push", "security_key"] | Omit = omit,
481548
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
482549
# The extra values given here take precedence over values defined on the client or passed to this method.
483550
extra_headers: Headers | None = None,
@@ -493,6 +560,7 @@ async def submit(
493560
{
494561
"field_values": field_values,
495562
"sso_button": sso_button,
563+
"selected_mfa_type": selected_mfa_type,
496564
},
497565
invocation_submit_params.InvocationSubmitParams,
498566
),

src/kernel/types/agents/agent_auth_invocation_response.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,23 @@
77
from ..._models import BaseModel
88
from .discovered_field import DiscoveredField
99

10-
__all__ = ["AgentAuthInvocationResponse", "PendingSSOButton"]
10+
__all__ = ["AgentAuthInvocationResponse", "MfaOption", "PendingSSOButton"]
11+
12+
13+
class MfaOption(BaseModel):
14+
"""An MFA method option for verification"""
15+
16+
label: str
17+
"""The visible option text"""
18+
19+
type: Literal["sms", "call", "email", "totp", "push", "security_key"]
20+
"""The MFA delivery method type"""
21+
22+
description: Optional[str] = None
23+
"""Additional instructions from the site"""
24+
25+
target: Optional[str] = None
26+
"""The masked destination (phone/email) if shown"""
1127

1228

1329
class PendingSSOButton(BaseModel):
@@ -63,6 +79,12 @@ class AgentAuthInvocationResponse(BaseModel):
6379
live_view_url: Optional[str] = None
6480
"""Browser live view URL for debugging the invocation"""
6581

82+
mfa_options: Optional[List[MfaOption]] = None
83+
"""
84+
MFA method options to choose from (present when step=awaiting_input and MFA
85+
selection is required)
86+
"""
87+
6688
pending_fields: Optional[List[DiscoveredField]] = None
6789
"""Fields currently awaiting input (present when step=awaiting_input)"""
6890

src/kernel/types/agents/auth/invocation_submit_params.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
from __future__ import annotations
44

55
from typing import Dict, Union
6-
from typing_extensions import Required, TypeAlias, TypedDict
6+
from typing_extensions import Literal, Required, TypeAlias, TypedDict
77

8-
__all__ = ["InvocationSubmitParams", "Variant0", "Variant1"]
8+
__all__ = ["InvocationSubmitParams", "Variant0", "Variant1", "Variant2"]
99

1010

1111
class Variant0(TypedDict, total=False):
@@ -18,4 +18,9 @@ class Variant1(TypedDict, total=False):
1818
"""Selector of SSO button to click"""
1919

2020

21-
InvocationSubmitParams: TypeAlias = Union[Variant0, Variant1]
21+
class Variant2(TypedDict, total=False):
22+
selected_mfa_type: Required[Literal["sms", "call", "email", "totp", "push", "security_key"]]
23+
"""The MFA delivery method type"""
24+
25+
26+
InvocationSubmitParams: TypeAlias = Union[Variant0, Variant1, Variant2]

tests/api_resources/agents/auth/test_invocations.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,52 @@ def test_path_params_submit_overload_2(self, client: Kernel) -> None:
255255
sso_button="xpath=//button[contains(text(), 'Continue with Google')]",
256256
)
257257

258+
@pytest.mark.skip(reason="Prism tests are disabled")
259+
@parametrize
260+
def test_method_submit_overload_3(self, client: Kernel) -> None:
261+
invocation = client.agents.auth.invocations.submit(
262+
invocation_id="invocation_id",
263+
selected_mfa_type="sms",
264+
)
265+
assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"])
266+
267+
@pytest.mark.skip(reason="Prism tests are disabled")
268+
@parametrize
269+
def test_raw_response_submit_overload_3(self, client: Kernel) -> None:
270+
response = client.agents.auth.invocations.with_raw_response.submit(
271+
invocation_id="invocation_id",
272+
selected_mfa_type="sms",
273+
)
274+
275+
assert response.is_closed is True
276+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
277+
invocation = response.parse()
278+
assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"])
279+
280+
@pytest.mark.skip(reason="Prism tests are disabled")
281+
@parametrize
282+
def test_streaming_response_submit_overload_3(self, client: Kernel) -> None:
283+
with client.agents.auth.invocations.with_streaming_response.submit(
284+
invocation_id="invocation_id",
285+
selected_mfa_type="sms",
286+
) as response:
287+
assert not response.is_closed
288+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
289+
290+
invocation = response.parse()
291+
assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"])
292+
293+
assert cast(Any, response.is_closed) is True
294+
295+
@pytest.mark.skip(reason="Prism tests are disabled")
296+
@parametrize
297+
def test_path_params_submit_overload_3(self, client: Kernel) -> None:
298+
with pytest.raises(ValueError, match=r"Expected a non-empty value for `invocation_id` but received ''"):
299+
client.agents.auth.invocations.with_raw_response.submit(
300+
invocation_id="",
301+
selected_mfa_type="sms",
302+
)
303+
258304

259305
class TestAsyncInvocations:
260306
parametrize = pytest.mark.parametrize(
@@ -495,3 +541,49 @@ async def test_path_params_submit_overload_2(self, async_client: AsyncKernel) ->
495541
invocation_id="",
496542
sso_button="xpath=//button[contains(text(), 'Continue with Google')]",
497543
)
544+
545+
@pytest.mark.skip(reason="Prism tests are disabled")
546+
@parametrize
547+
async def test_method_submit_overload_3(self, async_client: AsyncKernel) -> None:
548+
invocation = await async_client.agents.auth.invocations.submit(
549+
invocation_id="invocation_id",
550+
selected_mfa_type="sms",
551+
)
552+
assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"])
553+
554+
@pytest.mark.skip(reason="Prism tests are disabled")
555+
@parametrize
556+
async def test_raw_response_submit_overload_3(self, async_client: AsyncKernel) -> None:
557+
response = await async_client.agents.auth.invocations.with_raw_response.submit(
558+
invocation_id="invocation_id",
559+
selected_mfa_type="sms",
560+
)
561+
562+
assert response.is_closed is True
563+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
564+
invocation = await response.parse()
565+
assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"])
566+
567+
@pytest.mark.skip(reason="Prism tests are disabled")
568+
@parametrize
569+
async def test_streaming_response_submit_overload_3(self, async_client: AsyncKernel) -> None:
570+
async with async_client.agents.auth.invocations.with_streaming_response.submit(
571+
invocation_id="invocation_id",
572+
selected_mfa_type="sms",
573+
) as response:
574+
assert not response.is_closed
575+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
576+
577+
invocation = await response.parse()
578+
assert_matches_type(AgentAuthSubmitResponse, invocation, path=["response"])
579+
580+
assert cast(Any, response.is_closed) is True
581+
582+
@pytest.mark.skip(reason="Prism tests are disabled")
583+
@parametrize
584+
async def test_path_params_submit_overload_3(self, async_client: AsyncKernel) -> None:
585+
with pytest.raises(ValueError, match=r"Expected a non-empty value for `invocation_id` but received ''"):
586+
await async_client.agents.auth.invocations.with_raw_response.submit(
587+
invocation_id="",
588+
selected_mfa_type="sms",
589+
)

tests/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ def test_base_url_env(self) -> None:
578578
Kernel(api_key=api_key, _strict_response_validation=True, environment="production")
579579

580580
client = Kernel(base_url=None, api_key=api_key, _strict_response_validation=True, environment="production")
581-
assert str(client.base_url).startswith("https://api.onkernel.com/")
581+
assert str(client.base_url).startswith("https://api.kernel.com/")
582582

583583
client.close()
584584

@@ -1415,7 +1415,7 @@ async def test_base_url_env(self) -> None:
14151415
client = AsyncKernel(
14161416
base_url=None, api_key=api_key, _strict_response_validation=True, environment="production"
14171417
)
1418-
assert str(client.base_url).startswith("https://api.onkernel.com/")
1418+
assert str(client.base_url).startswith("https://api.kernel.com/")
14191419

14201420
await client.close()
14211421

0 commit comments

Comments
 (0)