From 4a42940d783536450c225b7a8ed9e851ee6c6333 Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:09:55 +0000 Subject: [PATCH 1/8] feat(api_keys)!: SDK surface change: Symbol "ValidateApiKey" was removed --- .../api_keys/models/validate_api_key.py | 33 +++---------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/workos/api_keys/models/validate_api_key.py b/src/workos/api_keys/models/validate_api_key.py index 276ccaee..36737894 100644 --- a/src/workos/api_keys/models/validate_api_key.py +++ b/src/workos/api_keys/models/validate_api_key.py @@ -1,32 +1,9 @@ # This file is auto-generated by oagen. Do not edit. -from __future__ import annotations +from typing import TypeAlias -from dataclasses import dataclass -from typing import Any +from workos.sso.models.create_saml_idp_signing_certificate import ( + CreateSAMLIdpSigningCertificate, +) -from workos._types import _raise_deserialize_error - - -@dataclass(slots=True) -class ValidateApiKey: - """Validate Api Key model.""" - - value: str - """The value for an API key.""" - - @classmethod - def from_dict(cls, data: dict[str, Any]) -> ValidateApiKey: - """Deserialize from a dictionary.""" - try: - return cls( - value=data["value"], - ) - except (KeyError, ValueError) as e: - _raise_deserialize_error("ValidateApiKey", e) - - def to_dict(self) -> dict[str, Any]: - """Serialize to a dictionary.""" - result: dict[str, Any] = {} - result["value"] = self.value - return result +ValidateApiKey: TypeAlias = CreateSAMLIdpSigningCertificate From 259475cb32929ba97ee886002298f91a90e9c8d6 Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:09:55 +0000 Subject: [PATCH 2/8] feat(audit_logs)!: SDK surface change: Parameter "retention_period_in_days" renamed to "retention" on "AuditLogs.update_organization_audit_logs_retention" --- src/workos/audit_logs/__init__.py | 5 +- src/workos/audit_logs/_resource.py | 43 +++++++++--- ...e_audit_logs_retention_retention_period.py | 68 +++++++++++++++++++ .../models/update_audit_logs_retention.py | 28 ++++++-- .../fixtures/update_audit_logs_retention.json | 1 + tests/test_audit_logs.py | 18 +++-- 6 files changed, 143 insertions(+), 20 deletions(-) create mode 100644 src/workos/common/models/update_audit_logs_retention_retention_period.py diff --git a/src/workos/audit_logs/__init__.py b/src/workos/audit_logs/__init__.py index 0184b08f..c5e11b40 100644 --- a/src/workos/audit_logs/__init__.py +++ b/src/workos/audit_logs/__init__.py @@ -1,4 +1,7 @@ # This file is auto-generated by oagen. Do not edit. -from ._resource import AuditLogs as AuditLogs, AsyncAuditLogs as AsyncAuditLogs +from ._resource import AsyncAuditLogs as AsyncAuditLogs +from ._resource import AuditLogs as AuditLogs +from ._resource import RetentionPeriod as RetentionPeriod +from ._resource import RetentionPeriodInDays as RetentionPeriodInDays from .models import * diff --git a/src/workos/audit_logs/_resource.py b/src/workos/audit_logs/_resource.py index f7388c78..49c1437b 100644 --- a/src/workos/audit_logs/_resource.py +++ b/src/workos/audit_logs/_resource.py @@ -7,7 +7,12 @@ if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient +from dataclasses import dataclass + from workos.common.models.pagination_order import PaginationOrder +from workos.common.models.update_audit_logs_retention_retention_period import ( + UpdateAuditLogsRetentionRetentionPeriod, +) from workos.organizations.models.audit_logs_retention import AuditLogsRetention from .._pagination import AsyncPage, SyncPage @@ -23,6 +28,20 @@ ) +@dataclass +class RetentionPeriod: + """Identify retention period.""" + + retention_period: UpdateAuditLogsRetentionRetentionPeriod | str + + +@dataclass +class RetentionPeriodInDays: + """Identify retention period in days.""" + + retention_period_in_days: int + + class AuditLogs: """Audit Logs API resources.""" @@ -63,7 +82,7 @@ def update_organization_audit_logs_retention( self, id: str, *, - retention_period_in_days: int, + retention: RetentionPeriod | RetentionPeriodInDays, request_options: RequestOptions | None = None, ) -> AuditLogsRetention: """Set Retention @@ -72,7 +91,7 @@ def update_organization_audit_logs_retention( Args: id: Unique identifier of the Organization. - retention_period_in_days: The number of days Audit Log events will be retained. Valid values are `30` and `365`. + retention: Identifies the retention. One of: RetentionPeriod, RetentionPeriodInDays. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -85,9 +104,11 @@ def update_organization_audit_logs_retention( RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ - body: dict[str, Any] = { - "retention_period_in_days": retention_period_in_days, - } + body: dict[str, Any] = {} + if isinstance(retention, RetentionPeriod): + body["retention_period"] = enum_value(retention.retention_period) + elif isinstance(retention, RetentionPeriodInDays): + body["retention_period_in_days"] = retention.retention_period_in_days return self._client.request( method="put", path=("organizations", str(id), "audit_logs_retention"), @@ -418,7 +439,7 @@ async def update_organization_audit_logs_retention( self, id: str, *, - retention_period_in_days: int, + retention: RetentionPeriod | RetentionPeriodInDays, request_options: RequestOptions | None = None, ) -> AuditLogsRetention: """Set Retention @@ -427,7 +448,7 @@ async def update_organization_audit_logs_retention( Args: id: Unique identifier of the Organization. - retention_period_in_days: The number of days Audit Log events will be retained. Valid values are `30` and `365`. + retention: Identifies the retention. One of: RetentionPeriod, RetentionPeriodInDays. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -440,9 +461,11 @@ async def update_organization_audit_logs_retention( RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ - body: dict[str, Any] = { - "retention_period_in_days": retention_period_in_days, - } + body: dict[str, Any] = {} + if isinstance(retention, RetentionPeriod): + body["retention_period"] = enum_value(retention.retention_period) + elif isinstance(retention, RetentionPeriodInDays): + body["retention_period_in_days"] = retention.retention_period_in_days return await self._client.request( method="put", path=("organizations", str(id), "audit_logs_retention"), diff --git a/src/workos/common/models/update_audit_logs_retention_retention_period.py b/src/workos/common/models/update_audit_logs_retention_retention_period.py new file mode 100644 index 00000000..23d69608 --- /dev/null +++ b/src/workos/common/models/update_audit_logs_retention_retention_period.py @@ -0,0 +1,68 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of update audit logs retention retention period values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class UpdateAuditLogsRetentionRetentionPeriod(str, Enum): + """Known values for UpdateAuditLogsRetentionRetentionPeriod.""" + + VALUE_1_MONTH = "1_MONTH" + VALUE_2_MONTHS = "2_MONTHS" + VALUE_3_MONTHS = "3_MONTHS" + VALUE_4_MONTHS = "4_MONTHS" + VALUE_5_MONTHS = "5_MONTHS" + VALUE_6_MONTHS = "6_MONTHS" + VALUE_7_MONTHS = "7_MONTHS" + VALUE_8_MONTHS = "8_MONTHS" + VALUE_9_MONTHS = "9_MONTHS" + VALUE_10_MONTHS = "10_MONTHS" + VALUE_11_MONTHS = "11_MONTHS" + VALUE_1_YEAR = "1_YEAR" + VALUE_2_YEARS = "2_YEARS" + VALUE_3_YEARS = "3_YEARS" + VALUE_4_YEARS = "4_YEARS" + VALUE_5_YEARS = "5_YEARS" + VALUE_6_YEARS = "6_YEARS" + VALUE_7_YEARS = "7_YEARS" + VALUE_8_YEARS = "8_YEARS" + VALUE_9_YEARS = "9_YEARS" + VALUE_10_YEARS = "10_YEARS" + + @classmethod + def _missing_(cls, value: object) -> UpdateAuditLogsRetentionRetentionPeriod | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +UpdateAuditLogsRetentionRetentionPeriodLiteral: TypeAlias = Literal[ + "1_MONTH", + "2_MONTHS", + "3_MONTHS", + "4_MONTHS", + "5_MONTHS", + "6_MONTHS", + "7_MONTHS", + "8_MONTHS", + "9_MONTHS", + "10_MONTHS", + "11_MONTHS", + "1_YEAR", + "2_YEARS", + "3_YEARS", + "4_YEARS", + "5_YEARS", + "6_YEARS", + "7_YEARS", + "8_YEARS", + "9_YEARS", + "10_YEARS", +] diff --git a/src/workos/organizations/models/update_audit_logs_retention.py b/src/workos/organizations/models/update_audit_logs_retention.py index 5ed3719c..f86fb3fd 100644 --- a/src/workos/organizations/models/update_audit_logs_retention.py +++ b/src/workos/organizations/models/update_audit_logs_retention.py @@ -3,24 +3,37 @@ from __future__ import annotations from dataclasses import dataclass +from enum import Enum from typing import Any from workos._types import _raise_deserialize_error +from workos.common.models.update_audit_logs_retention_retention_period import ( + UpdateAuditLogsRetentionRetentionPeriod, +) @dataclass(slots=True) class UpdateAuditLogsRetention: """Update Audit Logs Retention model.""" - retention_period_in_days: int - """The number of days Audit Log events will be retained. Valid values are `30` and `365`.""" + retention_period: UpdateAuditLogsRetentionRetentionPeriod | None = None + """The period Audit Log events will be retained. Valid values are `1_MONTH` through `11_MONTHS` in one-month increments and `1_YEAR` through `10_YEARS` in one-year increments. Mutually exclusive with `retention_period_in_days`.""" + retention_period_in_days: int | None = None + """The number of days Audit Log events will be retained. Valid values are `30` through `330` in 30-day increments and `365` through `3650` in 365-day increments. Deprecated: use `retention_period` instead. Mutually exclusive with `retention_period`. + + .. deprecated:: This field is deprecated.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> UpdateAuditLogsRetention: """Deserialize from a dictionary.""" try: return cls( - retention_period_in_days=data["retention_period_in_days"], + retention_period=UpdateAuditLogsRetentionRetentionPeriod( + _v_retention_period + ) + if (_v_retention_period := data.get("retention_period")) is not None + else None, + retention_period_in_days=data.get("retention_period_in_days"), ) except (KeyError, ValueError) as e: _raise_deserialize_error("UpdateAuditLogsRetention", e) @@ -28,5 +41,12 @@ def from_dict(cls, data: dict[str, Any]) -> UpdateAuditLogsRetention: def to_dict(self) -> dict[str, Any]: """Serialize to a dictionary.""" result: dict[str, Any] = {} - result["retention_period_in_days"] = self.retention_period_in_days + if self.retention_period is not None: + result["retention_period"] = ( + self.retention_period.value + if isinstance(self.retention_period, Enum) + else self.retention_period + ) + if self.retention_period_in_days is not None: + result["retention_period_in_days"] = self.retention_period_in_days return result diff --git a/tests/fixtures/update_audit_logs_retention.json b/tests/fixtures/update_audit_logs_retention.json index 0720427e..b242994f 100644 --- a/tests/fixtures/update_audit_logs_retention.json +++ b/tests/fixtures/update_audit_logs_retention.json @@ -1,3 +1,4 @@ { + "retention_period": "1_MONTH", "retention_period_in_days": 30 } diff --git a/tests/test_audit_logs.py b/tests/test_audit_logs.py index ce604506..af673d2a 100644 --- a/tests/test_audit_logs.py +++ b/tests/test_audit_logs.py @@ -15,6 +15,7 @@ UnprocessableEntityError, ) from workos._pagination import AsyncPage, SyncPage +from workos.audit_logs._resource import RetentionPeriod from workos.audit_logs.models import ( AuditLogAction, AuditLogEvent, @@ -22,7 +23,10 @@ AuditLogExport, AuditLogSchema, ) -from workos.common.models import PaginationOrder +from workos.common.models import ( + PaginationOrder, + UpdateAuditLogsRetentionRetentionPeriod, +) from workos.organizations.models import AuditLogsRetention @@ -43,15 +47,16 @@ def test_update_organization_audit_logs_retention(self, workos, httpx_mock): json=load_fixture("audit_logs_retention.json"), ) result = workos.audit_logs.update_organization_audit_logs_retention( - "test_id", retention_period_in_days=1 + "test_id", + retention=RetentionPeriod( + retention_period=UpdateAuditLogsRetentionRetentionPeriod("1_MONTH") + ), ) assert isinstance(result, AuditLogsRetention) assert result.retention_period_in_days == 30 request = httpx_mock.get_request() assert request.method == "PUT" assert request.url.path.endswith("/organizations/test_id/audit_logs_retention") - body = json.loads(request.content) - assert body["retention_period_in_days"] == 1 def test_list_actions(self, workos, httpx_mock): httpx_mock.add_response( @@ -276,7 +281,10 @@ async def test_update_organization_audit_logs_retention( ): httpx_mock.add_response(json=load_fixture("audit_logs_retention.json")) result = await async_workos.audit_logs.update_organization_audit_logs_retention( - "test_id", retention_period_in_days=1 + "test_id", + retention=RetentionPeriod( + retention_period=UpdateAuditLogsRetentionRetentionPeriod("1_MONTH") + ), ) assert isinstance(result, AuditLogsRetention) assert result.retention_period_in_days == 30 From 75a292facd3b654cc8437084f6ab5b365d27c336 Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:09:55 +0000 Subject: [PATCH 3/8] feat(sso)!: SDK surface change: Parameter type changed for "code" on "SSO.get_profile_and_token" --- src/workos/sso/__init__.py | 7 +- src/workos/sso/_resource.py | 1451 ++++++++++++++--- src/workos/sso/models/__init__.py | 48 + .../create_saml_idp_signing_certificate.py | 32 + .../models/saml_idp_signing_certificate.py | 63 + .../saml_idp_signing_certificate_list.py | 40 + .../models/saml_sp_encryption_certificate.py | 63 + .../saml_sp_encryption_certificate_list.py | 40 + .../sso/models/saml_sp_signing_certificate.py | 63 + src/workos/sso/models/sso_grant_type.py | 31 + src/workos/sso/models/token_query.py | 38 +- tests/test_sso.py | 368 ++++- tests/test_sso_models_round_trip.py | 505 ++++++ 13 files changed, 2477 insertions(+), 272 deletions(-) create mode 100644 src/workos/sso/models/create_saml_idp_signing_certificate.py create mode 100644 src/workos/sso/models/saml_idp_signing_certificate.py create mode 100644 src/workos/sso/models/saml_idp_signing_certificate_list.py create mode 100644 src/workos/sso/models/saml_sp_encryption_certificate.py create mode 100644 src/workos/sso/models/saml_sp_encryption_certificate_list.py create mode 100644 src/workos/sso/models/saml_sp_signing_certificate.py create mode 100644 src/workos/sso/models/sso_grant_type.py diff --git a/src/workos/sso/__init__.py b/src/workos/sso/__init__.py index be562a2a..66fa7567 100644 --- a/src/workos/sso/__init__.py +++ b/src/workos/sso/__init__.py @@ -1,4 +1,9 @@ # This file is auto-generated by oagen. Do not edit. -from ._resource import SSO as SSO, AsyncSSO as AsyncSSO +from ._resource import SSO as SSO +from ._resource import AsyncSSO as AsyncSSO +from ._resource import CreateProtocolOptionsOIDC as CreateProtocolOptionsOIDC +from ._resource import CreateProtocolOptionsSAML as CreateProtocolOptionsSAML +from ._resource import PatchProtocolOptionsOIDC as PatchProtocolOptionsOIDC +from ._resource import PatchProtocolOptionsSAML as PatchProtocolOptionsSAML from .models import * diff --git a/src/workos/sso/_resource.py b/src/workos/sso/_resource.py index 241ac076..224c3c02 100644 --- a/src/workos/sso/_resource.py +++ b/src/workos/sso/_resource.py @@ -7,20 +7,61 @@ if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient +from dataclasses import dataclass + from workos.common.models.pagination_order import PaginationOrder from .._pagination import AsyncPage, SyncPage -from .._types import RequestOptions, enum_value +from .._types import NOT_GIVEN, NotGiven, RequestOptions, enum_value from .models import ( Connection, ConnectionsConnectionType, + CreateConnectionAttributeMaps, + CreateConnectionOIDCOptions, + CreateConnectionSAMLOptions, + PatchConnectionAttributeMaps, + PatchConnectionOIDCOptions, + PatchConnectionSAMLOptions, Profile, + SAMLIdpSigningCertificate, + SAMLIdpSigningCertificateList, + SAMLSpEncryptionCertificate, + SAMLSpEncryptionCertificateList, + SAMLSpSigningCertificate, SSOLogoutAuthorizeResponse, SSOProvider, SSOTokenResponse, ) +@dataclass +class CreateProtocolOptionsSAML: + """Identify protocol options saml.""" + + saml_options: CreateConnectionSAMLOptions + + +@dataclass +class CreateProtocolOptionsOIDC: + """Identify protocol options oidc.""" + + oidc_options: CreateConnectionOIDCOptions + + +@dataclass +class PatchProtocolOptionsSAML: + """Identify protocol options saml.""" + + saml_options: PatchConnectionSAMLOptions + + +@dataclass +class PatchProtocolOptionsOIDC: + """Identify protocol options oidc.""" + + oidc_options: PatchConnectionOIDCOptions + + class SSO: """SSO API resources.""" @@ -89,52 +130,157 @@ def list_connections( request_options=request_options, ) - def get_connection( + def create_connection( self, - id: str, *, + organization_id: str, + name: str | None = None, + external_id: str | None = None, + connection_type: str | None = None, + attribute_maps: CreateConnectionAttributeMaps | None = None, + protocol_options: CreateProtocolOptionsSAML | CreateProtocolOptionsOIDC, request_options: RequestOptions | None = None, ) -> Connection: - """Get a Connection + """Create a Connection - Get the details of an existing connection. + Creates a new connection for an organization. Provide `saml_options` or `oidc_options` to configure the identity provider. When `external_id` matches an existing connection in the organization, that connection is returned instead of creating a duplicate. Args: - id: Unique identifier for the Connection. + organization_id: Unique identifier for the Organization in which the Connection resides. + name: A human-readable name for the Connection. This will most commonly be the organization's name. + external_id: The customer-owned identifier for the Connection. + connection_type: The type of the Connection. Only SAML and OIDC connection types may be created. When omitted, the type is inferred from the provided options. + attribute_maps: How IdP attributes or claims map onto WorkOS profile fields. Provided fields override the defaults for the connection type. + protocol_options: Identifies the protocol options. One of: CreateProtocolOptionsSAML, CreateProtocolOptionsOIDC. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: Connection Raises: + BadRequestError: If the request is malformed (400). AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ + body: dict[str, Any] = { + k: v + for k, v in { + "organization_id": organization_id, + "name": name, + "external_id": external_id, + "connection_type": connection_type, + "attribute_maps": attribute_maps.to_dict() + if attribute_maps is not None + else None, + }.items() + if v is not None + } + if isinstance(protocol_options, CreateProtocolOptionsSAML): + body["saml_options"] = protocol_options.saml_options.to_dict() + elif isinstance(protocol_options, CreateProtocolOptionsOIDC): + body["oidc_options"] = protocol_options.oidc_options.to_dict() return self._client.request( - method="get", - path=("connections", str(id)), + method="post", + path=("connections",), + body=body, model=Connection, request_options=request_options, ) - def delete_connection( + def list_connection_saml_idp_signing_certs( self, - id: str, + connection_id: str, + *, + request_options: RequestOptions | None = None, + ) -> SAMLIdpSigningCertificateList: + """List IdP signing certificates + + Lists every Identity Provider signing certificate on the connection, including expired ones, oldest first. + + Args: + connection_id: Unique identifier for the Connection. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SAMLIdpSigningCertificateList + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("connections", str(connection_id), "saml_idp_signing_certs"), + model=SAMLIdpSigningCertificateList, + request_options=request_options, + ) + + def create_connection_saml_idp_signing_cert( + self, + connection_id: str, + *, + value: str, + request_options: RequestOptions | None = None, + ) -> SAMLIdpSigningCertificate: + """Create an IdP signing certificate + + Adds an Identity Provider signing certificate to the connection, so SAML responses signed with its key can be verified. Use this to import a new certificate ahead of an Identity Provider rotation — the existing certificates keep working until they are deleted or expire. + + Args: + connection_id: Unique identifier for the Connection. + value: The PEM-encoded X.509 certificate. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SAMLIdpSigningCertificate + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + "value": value, + } + return self._client.request( + method="post", + path=("connections", str(connection_id), "saml_idp_signing_certs"), + body=body, + model=SAMLIdpSigningCertificate, + request_options=request_options, + ) + + def delete_connection_saml_idp_signing_cert( + self, + connection_id: str, + certificate_id: str, *, request_options: RequestOptions | None = None, ) -> None: - """Delete a Connection + """Delete an IdP signing certificate - Permanently deletes an existing connection. It cannot be undone. + Removes an Identity Provider signing certificate from the connection. The last remaining certificate cannot be deleted. A certificate still published in the Identity Provider metadata may be restored by a metadata refresh. Args: - id: Unique identifier for the Connection. + connection_id: Unique identifier for the Connection. + certificate_id: Unique identifier for the Identity Provider signing certificate. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Raises: + BadRequestError: If the request is malformed (400). AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). AuthenticationError: If the API key is invalid (401). @@ -143,402 +289,1173 @@ def delete_connection( """ self._client.request( method="delete", - path=("connections", str(id)), + path=( + "connections", + str(connection_id), + "saml_idp_signing_certs", + str(certificate_id), + ), request_options=request_options, ) - def get_authorization_url( + def list_connection_saml_sp_encryption_certs( self, + connection_id: str, *, - provider_scopes: list[str] | None = None, - provider_query_params: dict[str, str] | None = None, - client_id: str | None = None, - domain: str | None = None, - provider: SSOProvider | str | None = None, - redirect_uri: str, - state: str | None = None, - connection: str | None = None, - organization: str | None = None, - domain_hint: str | None = None, - login_hint: str | None = None, - nonce: str | None = None, - prompt: Literal["login"] | None = None, request_options: RequestOptions | None = None, - ) -> str: - """Initiate SSO + ) -> SAMLSpEncryptionCertificateList: + """List SP encryption certificates - Initiates the single sign-on flow. + Lists the public certificates the Identity Provider can use to encrypt SAML responses sent to WorkOS, including expired ones, oldest first. Args: - provider_scopes: Additional scopes to request from the identity provider. Applicable when using OAuth or OpenID Connect connections. - provider_query_params: Key/value pairs of query parameters to pass to the OAuth provider. Only applicable when using OAuth connections. - client_id: The unique identifier of the WorkOS environment client. Defaults to the client's configured client_id. - domain: (deprecated) Deprecated. Use `connection` or `organization` instead. Used to initiate SSO for a connection by domain. The domain must be associated with a connection in your WorkOS environment. - provider: Used to initiate OAuth authentication with various providers. - redirect_uri: Where to redirect the user after they complete the authentication process. You must use one of the redirect URIs configured via the [Redirects](https://dashboard.workos.com/redirects) page on the dashboard. - state: An optional parameter that can be used to encode arbitrary information to help restore application state between redirects. If included, the redirect URI received from WorkOS will contain the exact `state` that was passed. - connection: Used to initiate SSO for a connection. The value should be a WorkOS connection ID. - You can persist the WorkOS connection ID with application user or team identifiers. WorkOS will use the connection indicated by the connection parameter to direct the user to the corresponding IdP for authentication. - organization: Used to initiate SSO for an organization. The value should be a WorkOS organization ID. - You can persist the WorkOS organization ID with application user or team identifiers. WorkOS will use the organization ID to determine the appropriate connection and the IdP to direct the user to for authentication. - domain_hint: Can be used to pre-fill the domain field when initiating authentication with Microsoft OAuth or with a Google SAML connection type. - login_hint: Can be used to pre-fill the username/email address field of the IdP sign-in page for the user, if you know their username ahead of time. Currently supported for OAuth, OpenID Connect, Okta, Entra ID, and custom SAML connections. - nonce: A random string generated by the client that is used to mitigate replay attacks. - prompt: If set to `login`, forces re-authentication at the identity provider. For supported SAML providers this sets `ForceAuthn="true"` in the SAML request; providers that don't support it are unaffected. + connection_id: Unique identifier for the Connection. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: - str + SAMLSpEncryptionCertificateList Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ - params = { - k: v - for k, v in { - "provider_scopes": ",".join(str(v) for v in provider_scopes) - if provider_scopes is not None - else None, - "provider_query_params": provider_query_params, - "client_id": client_id, - "domain": domain, - "provider": enum_value(provider) if provider is not None else None, - "redirect_uri": redirect_uri, - "state": state, - "connection": connection, - "organization": organization, - "domain_hint": domain_hint, - "login_hint": login_hint, - "nonce": nonce, - "prompt": prompt, - }.items() - if v is not None - } - params["response_type"] = "code" - if "client_id" not in params and self._client.client_id is not None: - params["client_id"] = self._client.client_id - return self._client.build_url(("sso", "authorize"), params) + return self._client.request( + method="get", + path=("connections", str(connection_id), "saml_sp_encryption_certs"), + model=SAMLSpEncryptionCertificateList, + request_options=request_options, + ) - def get_logout_url( + def create_connection_saml_sp_encryption_cert( self, + connection_id: str, *, - token: str, request_options: RequestOptions | None = None, - ) -> str: - """Logout Redirect + ) -> SAMLSpEncryptionCertificate: + """Create an SP encryption certificate - Logout allows to sign out a user from your application by triggering the identity provider sign out flow. This `GET` endpoint should be a redirection, since the identity provider user will be identified in the browser session. + Generates a new encryption key pair for the connection and returns its public certificate. WorkOS holds the private key, so the request takes no body — to bring your own key pairs, provide `saml_options.sp_encryption_key_pairs` when creating the connection instead. Creating a certificate appends rather than replaces: every active private key is tried when decrypting, which lets a rotation overlap the old and new certificates. + + Args: + connection_id: Unique identifier for the Connection. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SAMLSpEncryptionCertificate + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="post", + path=("connections", str(connection_id), "saml_sp_encryption_certs"), + model=SAMLSpEncryptionCertificate, + request_options=request_options, + ) + + def delete_connection_saml_sp_encryption_cert( + self, + connection_id: str, + certificate_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an SP encryption certificate + + Removes an encryption key pair from the connection. SAML responses encrypted with its certificate can no longer be decrypted, so remove the certificate from the Identity Provider first when rotating. + + Args: + connection_id: Unique identifier for the Connection. + certificate_id: Unique identifier for the Service Provider encryption key pair. WorkOS holds the corresponding private key, which is never exposed. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="delete", + path=( + "connections", + str(connection_id), + "saml_sp_encryption_certs", + str(certificate_id), + ), + request_options=request_options, + ) + + def list_connection_saml_sp_signing_cert( + self, + connection_id: str, + *, + request_options: RequestOptions | None = None, + ) -> SAMLSpSigningCertificate: + """Get the SP signing certificate + + Returns the public certificate the Identity Provider can use to verify the signature of SAML requests sent by WorkOS. Responds with `404` when the connection has no request signing key pair. + + Args: + connection_id: Unique identifier for the Connection. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SAMLSpSigningCertificate + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("connections", str(connection_id), "saml_sp_signing_cert"), + model=SAMLSpSigningCertificate, + request_options=request_options, + ) + + def create_connection_saml_sp_signing_cert( + self, + connection_id: str, + *, + request_options: RequestOptions | None = None, + ) -> SAMLSpSigningCertificate: + """Create an SP signing certificate + + Generates a new request signing key pair for the connection and returns its public certificate. WorkOS holds the private key, so the request takes no body — to bring your own key pair, provide `saml_options.sp_signing_key_pair` when creating the connection instead. A connection signs with one key pair at a time: delete the existing certificate before creating its replacement. + + Args: + connection_id: Unique identifier for the Connection. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SAMLSpSigningCertificate + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="post", + path=("connections", str(connection_id), "saml_sp_signing_cert"), + model=SAMLSpSigningCertificate, + request_options=request_options, + ) + + def delete_connection_saml_sp_signing_cert( + self, + connection_id: str, + certificate_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete the SP signing certificate + + Removes the request signing key pair from the connection, after which SAML requests are sent unsigned. Delete the certificate before creating its replacement when rotating. + + Args: + connection_id: Unique identifier for the Connection. + certificate_id: Unique identifier for the Service Provider signing key pair. WorkOS holds the corresponding private key, which is never exposed. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="delete", + path=( + "connections", + str(connection_id), + "saml_sp_signing_cert", + str(certificate_id), + ), + request_options=request_options, + ) + + def get_connection( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> Connection: + """Get a Connection + + Get the details of an existing connection. + + Args: + id: Unique identifier for the Connection. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + Connection + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("connections", str(id)), + model=Connection, + request_options=request_options, + ) + + def update_connection( + self, + id: str, + *, + name: str | None = None, + external_id: str | None | NotGiven = NOT_GIVEN, + connection_type: str | None = None, + attribute_maps: PatchConnectionAttributeMaps | None = None, + protocol_options: PatchProtocolOptionsSAML + | PatchProtocolOptionsOIDC + | None = None, + request_options: RequestOptions | None = None, + ) -> Connection: + """Update a Connection + + Updates an existing connection. Only the provided fields are changed; fields that accept `null` are reset to their default behavior. + + Args: + id: Unique identifier for the Connection. + name: A human-readable name for the Connection. + external_id: The customer-owned identifier for the Connection. Set to `null` to stop tracking one. + connection_type: The type of the Connection. Immutable after creation — it may be sent, but only with the Connection current type. + attribute_maps: How IdP attributes or claims map onto WorkOS profile fields. Only the provided fields are updated. + protocol_options: Identifies the protocol options. One of: PatchProtocolOptionsSAML, PatchProtocolOptionsOIDC. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + Connection + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "name": name, + "connection_type": connection_type, + "attribute_maps": attribute_maps.to_dict() + if attribute_maps is not None + else None, + }.items() + if v is not None + } + if not isinstance(external_id, NotGiven): + body["external_id"] = external_id + if protocol_options is not None: + if isinstance(protocol_options, PatchProtocolOptionsSAML): + body["saml_options"] = protocol_options.saml_options.to_dict() + elif isinstance(protocol_options, PatchProtocolOptionsOIDC): + body["oidc_options"] = protocol_options.oidc_options.to_dict() + return self._client.request( + method="patch", + path=("connections", str(id)), + body=body, + model=Connection, + request_options=request_options, + ) + + def delete_connection( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete a Connection + + Permanently deletes an existing connection. It cannot be undone. + + Args: + id: Unique identifier for the Connection. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="delete", + path=("connections", str(id)), + request_options=request_options, + ) + + def get_authorization_url( + self, + *, + provider_scopes: list[str] | None = None, + provider_query_params: dict[str, str] | None = None, + client_id: str | None = None, + domain: str | None = None, + provider: SSOProvider | str | None = None, + redirect_uri: str, + state: str | None = None, + connection: str | None = None, + organization: str | None = None, + domain_hint: str | None = None, + login_hint: str | None = None, + nonce: str | None = None, + prompt: Literal["login"] | None = None, + request_options: RequestOptions | None = None, + ) -> str: + """Initiate SSO + + Initiates the single sign-on flow. + + Args: + provider_scopes: Additional scopes to request from the identity provider. Applicable when using OAuth or OpenID Connect connections. + provider_query_params: Key/value pairs of query parameters to pass to the OAuth provider. Only applicable when using OAuth connections. + client_id: The unique identifier of the WorkOS environment client. Defaults to the client's configured client_id. + domain: (deprecated) Deprecated. Use `connection` or `organization` instead. Used to initiate SSO for a connection by domain. The domain must be associated with a connection in your WorkOS environment. + provider: Used to initiate OAuth authentication with various providers. + redirect_uri: Where to redirect the user after they complete the authentication process. You must use one of the redirect URIs configured via the [Redirects](https://dashboard.workos.com/redirects) page on the dashboard. + state: An optional parameter that can be used to encode arbitrary information to help restore application state between redirects. If included, the redirect URI received from WorkOS will contain the exact `state` that was passed. + connection: Used to initiate SSO for a connection. The value should be a WorkOS connection ID. + You can persist the WorkOS connection ID with application user or team identifiers. WorkOS will use the connection indicated by the connection parameter to direct the user to the corresponding IdP for authentication. + organization: Used to initiate SSO for an organization. The value should be a WorkOS organization ID. + You can persist the WorkOS organization ID with application user or team identifiers. WorkOS will use the organization ID to determine the appropriate connection and the IdP to direct the user to for authentication. + domain_hint: Can be used to pre-fill the domain field when initiating authentication with Microsoft OAuth or with a Google SAML connection type. + login_hint: Can be used to pre-fill the username/email address field of the IdP sign-in page for the user, if you know their username ahead of time. Currently supported for OAuth, OpenID Connect, Okta, Entra ID, and custom SAML connections. + nonce: A random string generated by the client that is used to mitigate replay attacks. + prompt: If set to `login`, forces re-authentication at the identity provider. For supported SAML providers this sets `ForceAuthn="true"` in the SAML request; providers that don't support it are unaffected. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + str + + Raises: + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "provider_scopes": ",".join(str(v) for v in provider_scopes) + if provider_scopes is not None + else None, + "provider_query_params": provider_query_params, + "client_id": client_id, + "domain": domain, + "provider": enum_value(provider) if provider is not None else None, + "redirect_uri": redirect_uri, + "state": state, + "connection": connection, + "organization": organization, + "domain_hint": domain_hint, + "login_hint": login_hint, + "nonce": nonce, + "prompt": prompt, + }.items() + if v is not None + } + params["response_type"] = "code" + if "client_id" not in params and self._client.client_id is not None: + params["client_id"] = self._client.client_id + return self._client.build_url(("sso", "authorize"), params) + + def get_logout_url( + self, + *, + token: str, + request_options: RequestOptions | None = None, + ) -> str: + """Logout Redirect + + Logout allows to sign out a user from your application by triggering the identity provider sign out flow. This `GET` endpoint should be a redirection, since the identity provider user will be identified in the browser session. + + Before redirecting to this endpoint, you need to generate a short-lived logout token using the [Logout Authorize](https://workos.com/docs/reference/sso/logout/authorize) endpoint. + + Args: + token: The logout token returned from the [Logout Authorize](https://workos.com/docs/reference/sso/logout/authorize) endpoint. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + str + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "token": token, + }.items() + if v is not None + } + return self._client.build_url(("sso", "logout"), params) + + def authorize_logout( + self, + *, + profile_id: str, + request_options: RequestOptions | None = None, + ) -> SSOLogoutAuthorizeResponse: + """Logout Authorize + + You should call this endpoint from your server to generate a logout token which is required for the [Logout Redirect](https://workos.com/docs/reference/sso/logout) endpoint. + + Args: + profile_id: The unique ID of the profile to log out. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SSOLogoutAuthorizeResponse + + Raises: + BadRequestError: If the request is malformed (400). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + "profile_id": profile_id, + } + return self._client.request( + method="post", + path=("sso", "logout", "authorize"), + body=body, + model=SSOLogoutAuthorizeResponse, + request_options=request_options, + ) + + def get_profile( + self, + *, + access_token: str, + request_options: RequestOptions | None = None, + ) -> Profile: + """Get a User Profile + + Exchange an access token for a user's [Profile](https://workos.com/docs/reference/sso/profile). Because this profile is returned in the [Get a Profile and Token endpoint](https://workos.com/docs/reference/sso/profile/get-profile-and-token) your application usually does not need to call this endpoint. It is available for any authentication flows that require an additional endpoint to retrieve a user's profile. + + Args: + access_token: The bearer token for authentication. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + Profile + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + request_options = request_options or {} + request_options = { + **request_options, + "extra_headers": { + **(request_options.get("extra_headers") or {}), + "Authorization": f"Bearer {access_token}", + }, + } + return self._client.request( + method="get", + path=("sso", "profile"), + model=Profile, + request_options=request_options, + ) + + def get_profile_and_token( + self, + *, + code: str | None = None, + subject_token: str | None = None, + subject_token_type: Literal["urn:ietf:params:oauth:token-type:id_token"] + | None = None, + organization_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> SSOTokenResponse: + """Get a Profile and Token + + Get an access token along with the user [Profile](https://workos.com/docs/reference/sso/profile) using the code passed to your [Redirect URI](https://workos.com/docs/reference/sso/get-authorization-url/redirect-uri). + + Args: + code: The authorization code received from the authorization callback. Required when `grant_type` is `authorization_code`. + subject_token: The OIDC ID token to exchange. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:token-exchange`. Must be sent in the request body. + subject_token_type: The type of the subject token. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:token-exchange`. Must be sent in the request body. + organization_id: The ID of the organization whose connection the subject token is validated against. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:token-exchange`. Must be sent in the request body. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SSOTokenResponse + + Raises: + BadRequestError: If the request is malformed (400). + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "code": code, + "subject_token": subject_token, + "subject_token_type": subject_token_type, + "organization_id": organization_id, + }.items() + if v is not None + } + body["grant_type"] = "authorization_code" + if self._client.client_id is not None: + body["client_id"] = self._client.client_id + if self._client._api_key is not None: + body["client_secret"] = self._client._api_key + return self._client.request( + method="post", + path=("sso", "token"), + body=body, + model=SSOTokenResponse, + request_options=request_options, + ) + + # @oagen-ignore-start + + def get_authorization_url_with_pkce( + self, + *, + redirect_uri: str, + client_id: str | None = None, + connection: str | None = None, + organization: str | None = None, + provider: SSOProvider | str | None = None, + domain_hint: str | None = None, + login_hint: str | None = None, + provider_scopes: list[str] | None = None, + provider_query_params: dict[str, str] | None = None, + ) -> dict[str, str]: + """Generate an SSO authorization URL with auto-generated PKCE parameters.""" + from ..pkce import PKCE + + pkce = PKCE() + pair = pkce.generate() + state = pkce.generate_code_verifier(43) + resolved_client_id = client_id or self._client._require_client_id() + + params = { + k: v + for k, v in { + "client_id": resolved_client_id, + "redirect_uri": redirect_uri, + "response_type": "code", + "code_challenge": pair.code_challenge, + "code_challenge_method": "S256", + "state": state, + "connection": connection, + "organization": organization, + "provider": enum_value(provider) if provider is not None else None, + "domain_hint": domain_hint, + "login_hint": login_hint, + "provider_scopes": provider_scopes, + "provider_query_params": provider_query_params, + }.items() + if v is not None + } + url = self._client.build_url(("sso", "authorize"), params) + return {"url": url, "state": state, "code_verifier": pair.code_verifier} + + def get_profile_and_token_pkce( + self, + *, + code: str, + code_verifier: str, + client_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> SSOTokenResponse: + """Exchange an authorization code using a PKCE code_verifier for SSO.""" + resolved_client_id = client_id or self._client._require_client_id() + body: dict[str, Any] = { + "client_id": resolved_client_id, + "code": code, + "grant_type": "authorization_code", + "code_verifier": code_verifier, + } + if self._client._api_key: + body["client_secret"] = self._client._api_key + + return self._client.request( + method="post", + path=("sso", "token"), + body=body, + model=SSOTokenResponse, + request_options=request_options, + ) + + # @oagen-ignore-end + + +class AsyncSSO: + """SSO API resources (async).""" + + def __init__(self, client: AsyncWorkOSClient) -> None: + self._client = client + + async def list_connections( + self, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", + connection_type: ConnectionsConnectionType | str | None = None, + domain: str | None = None, + organization_id: str | None = None, + search: str | None = None, + request_options: RequestOptions | None = None, + ) -> AsyncPage[Connection]: + """List Connections + + Get a list of all of your existing connections matching the criteria specified. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. + order: Order the results by the creation time. Defaults to `desc`. + connection_type: Filter Connections by their type. + domain: Filter Connections by their associated domain. + organization_id: Filter Connections by their associated organization. + search: Searchable text to match against Connection names. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AsyncPage[Connection] + + Raises: + AuthorizationError: If the request is forbidden (403). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + "connection_type": enum_value(connection_type) + if connection_type is not None + else None, + "domain": domain, + "organization_id": organization_id, + "search": search, + }.items() + if v is not None + } + return await self._client.request_page( + method="get", + path=("connections",), + model=Connection, + params=params, + request_options=request_options, + ) + + async def create_connection( + self, + *, + organization_id: str, + name: str | None = None, + external_id: str | None = None, + connection_type: str | None = None, + attribute_maps: CreateConnectionAttributeMaps | None = None, + protocol_options: CreateProtocolOptionsSAML | CreateProtocolOptionsOIDC, + request_options: RequestOptions | None = None, + ) -> Connection: + """Create a Connection + + Creates a new connection for an organization. Provide `saml_options` or `oidc_options` to configure the identity provider. When `external_id` matches an existing connection in the organization, that connection is returned instead of creating a duplicate. + + Args: + organization_id: Unique identifier for the Organization in which the Connection resides. + name: A human-readable name for the Connection. This will most commonly be the organization's name. + external_id: The customer-owned identifier for the Connection. + connection_type: The type of the Connection. Only SAML and OIDC connection types may be created. When omitted, the type is inferred from the provided options. + attribute_maps: How IdP attributes or claims map onto WorkOS profile fields. Provided fields override the defaults for the connection type. + protocol_options: Identifies the protocol options. One of: CreateProtocolOptionsSAML, CreateProtocolOptionsOIDC. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + Connection + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "organization_id": organization_id, + "name": name, + "external_id": external_id, + "connection_type": connection_type, + "attribute_maps": attribute_maps.to_dict() + if attribute_maps is not None + else None, + }.items() + if v is not None + } + if isinstance(protocol_options, CreateProtocolOptionsSAML): + body["saml_options"] = protocol_options.saml_options.to_dict() + elif isinstance(protocol_options, CreateProtocolOptionsOIDC): + body["oidc_options"] = protocol_options.oidc_options.to_dict() + return await self._client.request( + method="post", + path=("connections",), + body=body, + model=Connection, + request_options=request_options, + ) + + async def list_connection_saml_idp_signing_certs( + self, + connection_id: str, + *, + request_options: RequestOptions | None = None, + ) -> SAMLIdpSigningCertificateList: + """List IdP signing certificates + + Lists every Identity Provider signing certificate on the connection, including expired ones, oldest first. + + Args: + connection_id: Unique identifier for the Connection. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SAMLIdpSigningCertificateList + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("connections", str(connection_id), "saml_idp_signing_certs"), + model=SAMLIdpSigningCertificateList, + request_options=request_options, + ) + + async def create_connection_saml_idp_signing_cert( + self, + connection_id: str, + *, + value: str, + request_options: RequestOptions | None = None, + ) -> SAMLIdpSigningCertificate: + """Create an IdP signing certificate + + Adds an Identity Provider signing certificate to the connection, so SAML responses signed with its key can be verified. Use this to import a new certificate ahead of an Identity Provider rotation — the existing certificates keep working until they are deleted or expire. + + Args: + connection_id: Unique identifier for the Connection. + value: The PEM-encoded X.509 certificate. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SAMLIdpSigningCertificate + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + "value": value, + } + return await self._client.request( + method="post", + path=("connections", str(connection_id), "saml_idp_signing_certs"), + body=body, + model=SAMLIdpSigningCertificate, + request_options=request_options, + ) + + async def delete_connection_saml_idp_signing_cert( + self, + connection_id: str, + certificate_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an IdP signing certificate + + Removes an Identity Provider signing certificate from the connection. The last remaining certificate cannot be deleted. A certificate still published in the Identity Provider metadata may be restored by a metadata refresh. + + Args: + connection_id: Unique identifier for the Connection. + certificate_id: Unique identifier for the Identity Provider signing certificate. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + await self._client.request( + method="delete", + path=( + "connections", + str(connection_id), + "saml_idp_signing_certs", + str(certificate_id), + ), + request_options=request_options, + ) + + async def list_connection_saml_sp_encryption_certs( + self, + connection_id: str, + *, + request_options: RequestOptions | None = None, + ) -> SAMLSpEncryptionCertificateList: + """List SP encryption certificates - Before redirecting to this endpoint, you need to generate a short-lived logout token using the [Logout Authorize](https://workos.com/docs/reference/sso/logout/authorize) endpoint. + Lists the public certificates the Identity Provider can use to encrypt SAML responses sent to WorkOS, including expired ones, oldest first. Args: - token: The logout token returned from the [Logout Authorize](https://workos.com/docs/reference/sso/logout/authorize) endpoint. + connection_id: Unique identifier for the Connection. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: - str + SAMLSpEncryptionCertificateList Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ - params = { - k: v - for k, v in { - "token": token, - }.items() - if v is not None - } - return self._client.build_url(("sso", "logout"), params) + return await self._client.request( + method="get", + path=("connections", str(connection_id), "saml_sp_encryption_certs"), + model=SAMLSpEncryptionCertificateList, + request_options=request_options, + ) - def authorize_logout( + async def create_connection_saml_sp_encryption_cert( self, + connection_id: str, *, - profile_id: str, request_options: RequestOptions | None = None, - ) -> SSOLogoutAuthorizeResponse: - """Logout Authorize + ) -> SAMLSpEncryptionCertificate: + """Create an SP encryption certificate - You should call this endpoint from your server to generate a logout token which is required for the [Logout Redirect](https://workos.com/docs/reference/sso/logout) endpoint. + Generates a new encryption key pair for the connection and returns its public certificate. WorkOS holds the private key, so the request takes no body — to bring your own key pairs, provide `saml_options.sp_encryption_key_pairs` when creating the connection instead. Creating a certificate appends rather than replaces: every active private key is tried when decrypting, which lets a rotation overlap the old and new certificates. Args: - profile_id: The unique ID of the profile to log out. + connection_id: Unique identifier for the Connection. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: - SSOLogoutAuthorizeResponse + SAMLSpEncryptionCertificate Raises: BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ - body: dict[str, Any] = { - "profile_id": profile_id, - } - return self._client.request( + return await self._client.request( method="post", - path=("sso", "logout", "authorize"), - body=body, - model=SSOLogoutAuthorizeResponse, + path=("connections", str(connection_id), "saml_sp_encryption_certs"), + model=SAMLSpEncryptionCertificate, request_options=request_options, ) - def get_profile( + async def delete_connection_saml_sp_encryption_cert( self, + connection_id: str, + certificate_id: str, *, - access_token: str, request_options: RequestOptions | None = None, - ) -> Profile: - """Get a User Profile + ) -> None: + """Delete an SP encryption certificate - Exchange an access token for a user's [Profile](https://workos.com/docs/reference/sso/profile). Because this profile is returned in the [Get a Profile and Token endpoint](https://workos.com/docs/reference/sso/profile/get-profile-and-token) your application usually does not need to call this endpoint. It is available for any authentication flows that require an additional endpoint to retrieve a user's profile. + Removes an encryption key pair from the connection. SAML responses encrypted with its certificate can no longer be decrypted, so remove the certificate from the Identity Provider first when rotating. Args: - access_token: The bearer token for authentication. + connection_id: Unique identifier for the Connection. + certificate_id: Unique identifier for the Service Provider encryption key pair. WorkOS holds the corresponding private key, which is never exposed. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - Returns: - Profile - Raises: - AuthenticationError: If the API key is invalid (401). + AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ - request_options = request_options or {} - request_options = { - **request_options, - "extra_headers": { - **(request_options.get("extra_headers") or {}), - "Authorization": f"Bearer {access_token}", - }, - } - return self._client.request( - method="get", - path=("sso", "profile"), - model=Profile, + await self._client.request( + method="delete", + path=( + "connections", + str(connection_id), + "saml_sp_encryption_certs", + str(certificate_id), + ), request_options=request_options, ) - def get_profile_and_token( + async def list_connection_saml_sp_signing_cert( self, + connection_id: str, *, - code: str, request_options: RequestOptions | None = None, - ) -> SSOTokenResponse: - """Get a Profile and Token + ) -> SAMLSpSigningCertificate: + """Get the SP signing certificate - Get an access token along with the user [Profile](https://workos.com/docs/reference/sso/profile) using the code passed to your [Redirect URI](https://workos.com/docs/reference/sso/get-authorization-url/redirect-uri). + Returns the public certificate the Identity Provider can use to verify the signature of SAML requests sent by WorkOS. Responds with `404` when the connection has no request signing key pair. Args: - code: The authorization code received from the authorization callback. + connection_id: Unique identifier for the Connection. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: - SSOTokenResponse + SAMLSpSigningCertificate Raises: BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). - UnprocessableEntityError: If the request data is unprocessable (422). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ - body: dict[str, Any] = { - "code": code, - } - body["grant_type"] = "authorization_code" - if self._client.client_id is not None: - body["client_id"] = self._client.client_id - if self._client._api_key is not None: - body["client_secret"] = self._client._api_key - return self._client.request( - method="post", - path=("sso", "token"), - body=body, - model=SSOTokenResponse, + return await self._client.request( + method="get", + path=("connections", str(connection_id), "saml_sp_signing_cert"), + model=SAMLSpSigningCertificate, request_options=request_options, ) - # @oagen-ignore-start - - def get_authorization_url_with_pkce( + async def create_connection_saml_sp_signing_cert( self, + connection_id: str, *, - redirect_uri: str, - client_id: str | None = None, - connection: str | None = None, - organization: str | None = None, - provider: SSOProvider | str | None = None, - domain_hint: str | None = None, - login_hint: str | None = None, - provider_scopes: list[str] | None = None, - provider_query_params: dict[str, str] | None = None, - ) -> dict[str, str]: - """Generate an SSO authorization URL with auto-generated PKCE parameters.""" - from ..pkce import PKCE + request_options: RequestOptions | None = None, + ) -> SAMLSpSigningCertificate: + """Create an SP signing certificate - pkce = PKCE() - pair = pkce.generate() - state = pkce.generate_code_verifier(43) - resolved_client_id = client_id or self._client._require_client_id() + Generates a new request signing key pair for the connection and returns its public certificate. WorkOS holds the private key, so the request takes no body — to bring your own key pair, provide `saml_options.sp_signing_key_pair` when creating the connection instead. A connection signs with one key pair at a time: delete the existing certificate before creating its replacement. - params = { - k: v - for k, v in { - "client_id": resolved_client_id, - "redirect_uri": redirect_uri, - "response_type": "code", - "code_challenge": pair.code_challenge, - "code_challenge_method": "S256", - "state": state, - "connection": connection, - "organization": organization, - "provider": enum_value(provider) if provider is not None else None, - "domain_hint": domain_hint, - "login_hint": login_hint, - "provider_scopes": provider_scopes, - "provider_query_params": provider_query_params, - }.items() - if v is not None - } - url = self._client.build_url(("sso", "authorize"), params) - return {"url": url, "state": state, "code_verifier": pair.code_verifier} + Args: + connection_id: Unique identifier for the Connection. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - def get_profile_and_token_pkce( - self, - *, - code: str, - code_verifier: str, - client_id: str | None = None, - request_options: RequestOptions | None = None, - ) -> SSOTokenResponse: - """Exchange an authorization code using a PKCE code_verifier for SSO.""" - resolved_client_id = client_id or self._client._require_client_id() - body: dict[str, Any] = { - "client_id": resolved_client_id, - "code": code, - "grant_type": "authorization_code", - "code_verifier": code_verifier, - } - if self._client._api_key: - body["client_secret"] = self._client._api_key + Returns: + SAMLSpSigningCertificate - return self._client.request( + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( method="post", - path=("sso", "token"), - body=body, - model=SSOTokenResponse, + path=("connections", str(connection_id), "saml_sp_signing_cert"), + model=SAMLSpSigningCertificate, request_options=request_options, ) - # @oagen-ignore-end + async def delete_connection_saml_sp_signing_cert( + self, + connection_id: str, + certificate_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete the SP signing certificate + Removes the request signing key pair from the connection, after which SAML requests are sent unsigned. Delete the certificate before creating its replacement when rotating. -class AsyncSSO: - """SSO API resources (async).""" + Args: + connection_id: Unique identifier for the Connection. + certificate_id: Unique identifier for the Service Provider signing key pair. WorkOS holds the corresponding private key, which is never exposed. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - def __init__(self, client: AsyncWorkOSClient) -> None: - self._client = client + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + await self._client.request( + method="delete", + path=( + "connections", + str(connection_id), + "saml_sp_signing_cert", + str(certificate_id), + ), + request_options=request_options, + ) - async def list_connections( + async def get_connection( self, + id: str, *, - limit: int | None = None, - before: str | None = None, - after: str | None = None, - order: PaginationOrder | str | None = "desc", - connection_type: ConnectionsConnectionType | str | None = None, - domain: str | None = None, - organization_id: str | None = None, - search: str | None = None, request_options: RequestOptions | None = None, - ) -> AsyncPage[Connection]: - """List Connections + ) -> Connection: + """Get a Connection - Get a list of all of your existing connections matching the criteria specified. + Get the details of an existing connection. Args: - limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. - order: Order the results by the creation time. Defaults to `desc`. - connection_type: Filter Connections by their type. - domain: Filter Connections by their associated domain. - organization_id: Filter Connections by their associated organization. - search: Searchable text to match against Connection names. + id: Unique identifier for the Connection. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: - AsyncPage[Connection] + Connection Raises: AuthorizationError: If the request is forbidden (403). - UnprocessableEntityError: If the request data is unprocessable (422). + NotFoundError: If the resource is not found (404). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ - params = { - k: v - for k, v in { - "limit": limit, - "before": before, - "after": after, - "order": enum_value(order) if order is not None else None, - "connection_type": enum_value(connection_type) - if connection_type is not None - else None, - "domain": domain, - "organization_id": organization_id, - "search": search, - }.items() - if v is not None - } - return await self._client.request_page( + return await self._client.request( method="get", - path=("connections",), + path=("connections", str(id)), model=Connection, - params=params, request_options=request_options, ) - async def get_connection( + async def update_connection( self, id: str, *, + name: str | None = None, + external_id: str | None | NotGiven = NOT_GIVEN, + connection_type: str | None = None, + attribute_maps: PatchConnectionAttributeMaps | None = None, + protocol_options: PatchProtocolOptionsSAML + | PatchProtocolOptionsOIDC + | None = None, request_options: RequestOptions | None = None, ) -> Connection: - """Get a Connection + """Update a Connection - Get the details of an existing connection. + Updates an existing connection. Only the provided fields are changed; fields that accept `null` are reset to their default behavior. Args: id: Unique identifier for the Connection. + name: A human-readable name for the Connection. + external_id: The customer-owned identifier for the Connection. Set to `null` to stop tracking one. + connection_type: The type of the Connection. Immutable after creation — it may be sent, but only with the Connection current type. + attribute_maps: How IdP attributes or claims map onto WorkOS profile fields. Only the provided fields are updated. + protocol_options: Identifies the protocol options. One of: PatchProtocolOptionsSAML, PatchProtocolOptionsOIDC. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: Connection Raises: + BadRequestError: If the request is malformed (400). AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ + body: dict[str, Any] = { + k: v + for k, v in { + "name": name, + "connection_type": connection_type, + "attribute_maps": attribute_maps.to_dict() + if attribute_maps is not None + else None, + }.items() + if v is not None + } + if not isinstance(external_id, NotGiven): + body["external_id"] = external_id + if protocol_options is not None: + if isinstance(protocol_options, PatchProtocolOptionsSAML): + body["saml_options"] = protocol_options.saml_options.to_dict() + elif isinstance(protocol_options, PatchProtocolOptionsOIDC): + body["oidc_options"] = protocol_options.oidc_options.to_dict() return await self._client.request( - method="get", + method="patch", path=("connections", str(id)), + body=body, model=Connection, request_options=request_options, ) @@ -754,7 +1671,11 @@ async def get_profile( async def get_profile_and_token( self, *, - code: str, + code: str | None = None, + subject_token: str | None = None, + subject_token_type: Literal["urn:ietf:params:oauth:token-type:id_token"] + | None = None, + organization_id: str | None = None, request_options: RequestOptions | None = None, ) -> SSOTokenResponse: """Get a Profile and Token @@ -762,7 +1683,10 @@ async def get_profile_and_token( Get an access token along with the user [Profile](https://workos.com/docs/reference/sso/profile) using the code passed to your [Redirect URI](https://workos.com/docs/reference/sso/get-authorization-url/redirect-uri). Args: - code: The authorization code received from the authorization callback. + code: The authorization code received from the authorization callback. Required when `grant_type` is `authorization_code`. + subject_token: The OIDC ID token to exchange. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:token-exchange`. Must be sent in the request body. + subject_token_type: The type of the subject token. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:token-exchange`. Must be sent in the request body. + organization_id: The ID of the organization whose connection the subject token is validated against. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:token-exchange`. Must be sent in the request body. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -777,7 +1701,14 @@ async def get_profile_and_token( ServerError: If the server returns a 5xx error. """ body: dict[str, Any] = { - "code": code, + k: v + for k, v in { + "code": code, + "subject_token": subject_token, + "subject_token_type": subject_token_type, + "organization_id": organization_id, + }.items() + if v is not None } body["grant_type"] = "authorization_code" if self._client.client_id is not None: diff --git a/src/workos/sso/models/__init__.py b/src/workos/sso/models/__init__.py index 8fd3e22e..dae804ef 100644 --- a/src/workos/sso/models/__init__.py +++ b/src/workos/sso/models/__init__.py @@ -5,10 +5,58 @@ from .connections_connection_type import ( ConnectionsConnectionType as ConnectionsConnectionType, ) +from .create_connection import CreateConnection as CreateConnection +from .create_connection_attribute_maps import ( + CreateConnectionAttributeMaps as CreateConnectionAttributeMaps, +) +from .create_connection_key_pair import ( + CreateConnectionKeyPair as CreateConnectionKeyPair, +) +from .create_connection_oidc_options import ( + CreateConnectionOIDCOptions as CreateConnectionOIDCOptions, +) +from .create_connection_saml_options import ( + CreateConnectionSAMLOptions as CreateConnectionSAMLOptions, +) +from .create_connection_standard_attributes import ( + CreateConnectionStandardAttributes as CreateConnectionStandardAttributes, +) +from .create_saml_idp_signing_certificate import ( + CreateSAMLIdpSigningCertificate as CreateSAMLIdpSigningCertificate, +) +from .patch_connection import PatchConnection as PatchConnection +from .patch_connection_attribute_maps import ( + PatchConnectionAttributeMaps as PatchConnectionAttributeMaps, +) +from .patch_connection_oidc_options import ( + PatchConnectionOIDCOptions as PatchConnectionOIDCOptions, +) +from .patch_connection_saml_options import ( + PatchConnectionSAMLOptions as PatchConnectionSAMLOptions, +) +from .patch_connection_standard_attributes import ( + PatchConnectionStandardAttributes as PatchConnectionStandardAttributes, +) from .profile import Profile as Profile +from .saml_idp_signing_certificate import ( + SAMLIdpSigningCertificate as SAMLIdpSigningCertificate, +) +from .saml_idp_signing_certificate_list import ( + SAMLIdpSigningCertificateList as SAMLIdpSigningCertificateList, +) +from .saml_sp_encryption_certificate import ( + SAMLSpEncryptionCertificate as SAMLSpEncryptionCertificate, +) +from .saml_sp_encryption_certificate_list import ( + SAMLSpEncryptionCertificateList as SAMLSpEncryptionCertificateList, +) +from .saml_sp_signing_certificate import ( + SAMLSpSigningCertificate as SAMLSpSigningCertificate, +) from .sso_authorize_url_response import ( SSOAuthorizeUrlResponse as SSOAuthorizeUrlResponse, ) +from .sso_grant_type import SSOGrantType as SSOGrantType from .sso_logout_authorize_request import ( SSOLogoutAuthorizeRequest as SSOLogoutAuthorizeRequest, ) diff --git a/src/workos/sso/models/create_saml_idp_signing_certificate.py b/src/workos/sso/models/create_saml_idp_signing_certificate.py new file mode 100644 index 00000000..50359e58 --- /dev/null +++ b/src/workos/sso/models/create_saml_idp_signing_certificate.py @@ -0,0 +1,32 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class CreateSAMLIdpSigningCertificate: + """Create SAML Idp Signing Certificate model.""" + + value: str + """The PEM-encoded X.509 certificate.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateSAMLIdpSigningCertificate: + """Deserialize from a dictionary.""" + try: + return cls( + value=data["value"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateSAMLIdpSigningCertificate", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["value"] = self.value + return result diff --git a/src/workos/sso/models/saml_idp_signing_certificate.py b/src/workos/sso/models/saml_idp_signing_certificate.py new file mode 100644 index 00000000..ae9204b0 --- /dev/null +++ b/src/workos/sso/models/saml_idp_signing_certificate.py @@ -0,0 +1,63 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + + +@dataclass(slots=True) +class SAMLIdpSigningCertificate: + """SAML Idp Signing Certificate model.""" + + object: Literal["saml_idp_signing_certificate"] + """Distinguishes the SAML Identity Provider signing certificate object.""" + id: str + """Unique identifier for the Identity Provider signing certificate.""" + value: str + """The PEM-encoded public X.509 certificate.""" + not_before: datetime | None + """When the certificate becomes valid.""" + not_after: datetime | None + """When the certificate expires.""" + created_at: datetime + """An ISO 8601 timestamp.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> SAMLIdpSigningCertificate: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "saml_idp_signing_certificate"), + id=data["id"], + value=data["value"], + not_before=_parse_datetime(_v_not_before) + if (_v_not_before := data["not_before"]) is not None + else None, + not_after=_parse_datetime(_v_not_after) + if (_v_not_after := data["not_after"]) is not None + else None, + created_at=_parse_datetime(data["created_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("SAMLIdpSigningCertificate", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["value"] = self.value + if self.not_before is not None: + result["not_before"] = _format_datetime(self.not_before) + else: + result["not_before"] = None + if self.not_after is not None: + result["not_after"] = _format_datetime(self.not_after) + else: + result["not_after"] = None + result["created_at"] = _format_datetime(self.created_at) + return result diff --git a/src/workos/sso/models/saml_idp_signing_certificate_list.py b/src/workos/sso/models/saml_idp_signing_certificate_list.py new file mode 100644 index 00000000..699de19b --- /dev/null +++ b/src/workos/sso/models/saml_idp_signing_certificate_list.py @@ -0,0 +1,40 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal, cast + +from workos._types import _raise_deserialize_error + +from .saml_idp_signing_certificate import SAMLIdpSigningCertificate + + +@dataclass(slots=True) +class SAMLIdpSigningCertificateList: + """SAML Idp Signing Certificate List model.""" + + object: Literal["list"] + data: list[SAMLIdpSigningCertificate] + """Every Identity Provider signing certificate on the Connection, including expired ones, oldest first.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> SAMLIdpSigningCertificateList: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "list"), + data=[ + SAMLIdpSigningCertificate.from_dict(cast(dict[str, Any], item)) + for item in cast(list[Any], data["data"]) + ], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("SAMLIdpSigningCertificateList", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["data"] = [item.to_dict() for item in self.data] + return result diff --git a/src/workos/sso/models/saml_sp_encryption_certificate.py b/src/workos/sso/models/saml_sp_encryption_certificate.py new file mode 100644 index 00000000..9ba2ef17 --- /dev/null +++ b/src/workos/sso/models/saml_sp_encryption_certificate.py @@ -0,0 +1,63 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + + +@dataclass(slots=True) +class SAMLSpEncryptionCertificate: + """SAML Sp Encryption Certificate model.""" + + object: Literal["saml_sp_encryption_certificate"] + """Distinguishes the SAML Service Provider encryption certificate object.""" + id: str + """Unique identifier for the Service Provider encryption key pair. WorkOS holds the corresponding private key, which is never exposed.""" + value: str + """The PEM-encoded public X.509 certificate.""" + not_before: datetime | None + """When the certificate becomes valid.""" + not_after: datetime | None + """When the certificate expires.""" + created_at: datetime + """An ISO 8601 timestamp.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> SAMLSpEncryptionCertificate: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "saml_sp_encryption_certificate"), + id=data["id"], + value=data["value"], + not_before=_parse_datetime(_v_not_before) + if (_v_not_before := data["not_before"]) is not None + else None, + not_after=_parse_datetime(_v_not_after) + if (_v_not_after := data["not_after"]) is not None + else None, + created_at=_parse_datetime(data["created_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("SAMLSpEncryptionCertificate", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["value"] = self.value + if self.not_before is not None: + result["not_before"] = _format_datetime(self.not_before) + else: + result["not_before"] = None + if self.not_after is not None: + result["not_after"] = _format_datetime(self.not_after) + else: + result["not_after"] = None + result["created_at"] = _format_datetime(self.created_at) + return result diff --git a/src/workos/sso/models/saml_sp_encryption_certificate_list.py b/src/workos/sso/models/saml_sp_encryption_certificate_list.py new file mode 100644 index 00000000..11cb8047 --- /dev/null +++ b/src/workos/sso/models/saml_sp_encryption_certificate_list.py @@ -0,0 +1,40 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal, cast + +from workos._types import _raise_deserialize_error + +from .saml_sp_encryption_certificate import SAMLSpEncryptionCertificate + + +@dataclass(slots=True) +class SAMLSpEncryptionCertificateList: + """SAML Sp Encryption Certificate List model.""" + + object: Literal["list"] + data: list[SAMLSpEncryptionCertificate] + """Every Service Provider encryption certificate on the Connection, including expired ones.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> SAMLSpEncryptionCertificateList: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "list"), + data=[ + SAMLSpEncryptionCertificate.from_dict(cast(dict[str, Any], item)) + for item in cast(list[Any], data["data"]) + ], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("SAMLSpEncryptionCertificateList", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["data"] = [item.to_dict() for item in self.data] + return result diff --git a/src/workos/sso/models/saml_sp_signing_certificate.py b/src/workos/sso/models/saml_sp_signing_certificate.py new file mode 100644 index 00000000..52a060ee --- /dev/null +++ b/src/workos/sso/models/saml_sp_signing_certificate.py @@ -0,0 +1,63 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + + +@dataclass(slots=True) +class SAMLSpSigningCertificate: + """SAML Sp Signing Certificate model.""" + + object: Literal["saml_sp_signing_certificate"] + """Distinguishes the SAML Service Provider signing certificate object.""" + id: str + """Unique identifier for the Service Provider signing key pair. WorkOS holds the corresponding private key, which is never exposed.""" + value: str + """The PEM-encoded public X.509 certificate.""" + not_before: datetime | None + """When the certificate becomes valid.""" + not_after: datetime | None + """When the certificate expires.""" + created_at: datetime + """An ISO 8601 timestamp.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> SAMLSpSigningCertificate: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "saml_sp_signing_certificate"), + id=data["id"], + value=data["value"], + not_before=_parse_datetime(_v_not_before) + if (_v_not_before := data["not_before"]) is not None + else None, + not_after=_parse_datetime(_v_not_after) + if (_v_not_after := data["not_after"]) is not None + else None, + created_at=_parse_datetime(data["created_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("SAMLSpSigningCertificate", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["value"] = self.value + if self.not_before is not None: + result["not_before"] = _format_datetime(self.not_before) + else: + result["not_before"] = None + if self.not_after is not None: + result["not_after"] = _format_datetime(self.not_after) + else: + result["not_after"] = None + result["created_at"] = _format_datetime(self.created_at) + return result diff --git a/src/workos/sso/models/sso_grant_type.py b/src/workos/sso/models/sso_grant_type.py new file mode 100644 index 00000000..f3057861 --- /dev/null +++ b/src/workos/sso/models/sso_grant_type.py @@ -0,0 +1,31 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of sso grant type values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class SSOGrantType(str, Enum): + """Known values for SSOGrantType.""" + + AUTHORIZATION_CODE = "authorization_code" + URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE = ( + "urn:ietf:params:oauth:grant-type:token-exchange" + ) + + @classmethod + def _missing_(cls, value: object) -> SSOGrantType | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +SSOGrantTypeLiteral: TypeAlias = Literal[ + "authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange" +] diff --git a/src/workos/sso/models/token_query.py b/src/workos/sso/models/token_query.py index 355aa3c0..962ca4b1 100644 --- a/src/workos/sso/models/token_query.py +++ b/src/workos/sso/models/token_query.py @@ -3,9 +3,11 @@ from __future__ import annotations from dataclasses import dataclass +from enum import Enum from typing import Any, Literal from workos._types import _raise_deserialize_error +from workos.common.models.token_query_grant_type import TokenQueryGrantType @dataclass(slots=True) @@ -16,10 +18,18 @@ class TokenQuery: """The client ID of the WorkOS environment.""" client_secret: str """The client secret of the WorkOS environment.""" - code: str - """The authorization code received from the authorization callback.""" - grant_type: Literal["authorization_code"] + grant_type: TokenQueryGrantType """The grant type for the token request.""" + code: str | None = None + """The authorization code received from the authorization callback. Required when `grant_type` is `authorization_code`.""" + subject_token: str | None = None + """The OIDC ID token to exchange. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:token-exchange`. Must be sent in the request body.""" + subject_token_type: Literal["urn:ietf:params:oauth:token-type:id_token"] | None = ( + None + ) + """The type of the subject token. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:token-exchange`. Must be sent in the request body.""" + organization_id: str | None = None + """The ID of the organization whose connection the subject token is validated against. Required when `grant_type` is `urn:ietf:params:oauth:grant-type:token-exchange`. Must be sent in the request body.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> TokenQuery: @@ -28,8 +38,11 @@ def from_dict(cls, data: dict[str, Any]) -> TokenQuery: return cls( client_id=data["client_id"], client_secret=data["client_secret"], - code=data["code"], - grant_type=data.get("grant_type", "authorization_code"), + grant_type=TokenQueryGrantType(data["grant_type"]), + code=data.get("code"), + subject_token=data.get("subject_token"), + subject_token_type=data.get("subject_token_type"), + organization_id=data.get("organization_id"), ) except (KeyError, ValueError) as e: _raise_deserialize_error("TokenQuery", e) @@ -39,6 +52,17 @@ def to_dict(self) -> dict[str, Any]: result: dict[str, Any] = {} result["client_id"] = self.client_id result["client_secret"] = self.client_secret - result["code"] = self.code - result["grant_type"] = self.grant_type + result["grant_type"] = ( + self.grant_type.value + if isinstance(self.grant_type, Enum) + else self.grant_type + ) + if self.code is not None: + result["code"] = self.code + if self.subject_token is not None: + result["subject_token"] = self.subject_token + if self.subject_token_type is not None: + result["subject_token_type"] = self.subject_token_type + if self.organization_id is not None: + result["organization_id"] = self.organization_id return result diff --git a/tests/test_sso.py b/tests/test_sso.py index d1121068..24408cc3 100644 --- a/tests/test_sso.py +++ b/tests/test_sso.py @@ -16,10 +16,21 @@ ) from workos._pagination import AsyncPage, SyncPage from workos.common.models import PaginationOrder +from workos.sso._resource import ( + CreateProtocolOptionsSAML, + PatchProtocolOptionsSAML, +) from workos.sso.models import ( Connection, ConnectionsConnectionType, + CreateConnectionSAMLOptions, + PatchConnectionSAMLOptions, Profile, + SAMLIdpSigningCertificate, + SAMLIdpSigningCertificateList, + SAMLSpEncryptionCertificate, + SAMLSpEncryptionCertificateList, + SAMLSpSigningCertificate, SSOLogoutAuthorizeResponse, SSOTokenResponse, ) @@ -63,6 +74,153 @@ def test_list_connections_encodes_query_params(self, workos, httpx_mock): assert request.url.params["organization_id"] == "value organization_id/test" assert request.url.params["search"] == "value search/test" + def test_create_connection(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("connection.json"), + ) + result = workos.sso.create_connection( + organization_id="test_organization_id", + protocol_options=CreateProtocolOptionsSAML( + saml_options=CreateConnectionSAMLOptions.from_dict( + load_fixture("create_connection_saml_options.json") + ) + ), + ) + assert isinstance(result, Connection) + assert result.object == "connection" + assert result.id == "conn_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/connections") + body = json.loads(request.content) + assert body["organization_id"] == "test_organization_id" + + def test_list_connection_saml_idp_signing_certs(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("saml_idp_signing_certificate_list.json"), + ) + result = workos.sso.list_connection_saml_idp_signing_certs("test_connectionId") + assert isinstance(result, SAMLIdpSigningCertificateList) + assert result.object == "list" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_idp_signing_certs" + ) + + def test_create_connection_saml_idp_signing_cert(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("saml_idp_signing_certificate.json"), + ) + result = workos.sso.create_connection_saml_idp_signing_cert( + "test_connectionId", value="test_value" + ) + assert isinstance(result, SAMLIdpSigningCertificate) + assert result.object == "saml_idp_signing_certificate" + assert result.id == "saml_x509_cert_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_idp_signing_certs" + ) + body = json.loads(request.content) + assert body["value"] == "test_value" + + def test_delete_connection_saml_idp_signing_cert(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.sso.delete_connection_saml_idp_signing_cert( + "test_connectionId", "test_certificateId" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_idp_signing_certs/test_certificateId" + ) + + def test_list_connection_saml_sp_encryption_certs(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("saml_sp_encryption_certificate_list.json"), + ) + result = workos.sso.list_connection_saml_sp_encryption_certs( + "test_connectionId" + ) + assert isinstance(result, SAMLSpEncryptionCertificateList) + assert result.object == "list" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_encryption_certs" + ) + + def test_create_connection_saml_sp_encryption_cert(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("saml_sp_encryption_certificate.json"), + ) + result = workos.sso.create_connection_saml_sp_encryption_cert( + "test_connectionId" + ) + assert isinstance(result, SAMLSpEncryptionCertificate) + assert result.object == "saml_sp_encryption_certificate" + assert result.id == "saml_enc_key_pair_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_encryption_certs" + ) + + def test_delete_connection_saml_sp_encryption_cert(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.sso.delete_connection_saml_sp_encryption_cert( + "test_connectionId", "test_certificateId" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_encryption_certs/test_certificateId" + ) + + def test_list_connection_saml_sp_signing_cert(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("saml_sp_signing_certificate.json"), + ) + result = workos.sso.list_connection_saml_sp_signing_cert("test_connectionId") + assert isinstance(result, SAMLSpSigningCertificate) + assert result.object == "saml_sp_signing_certificate" + assert result.id == "saml_party_trust_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_signing_cert" + ) + + def test_create_connection_saml_sp_signing_cert(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("saml_sp_signing_certificate.json"), + ) + result = workos.sso.create_connection_saml_sp_signing_cert("test_connectionId") + assert isinstance(result, SAMLSpSigningCertificate) + assert result.object == "saml_sp_signing_certificate" + assert result.id == "saml_party_trust_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_signing_cert" + ) + + def test_delete_connection_saml_sp_signing_cert(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.sso.delete_connection_saml_sp_signing_cert( + "test_connectionId", "test_certificateId" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_signing_cert/test_certificateId" + ) + def test_get_connection(self, workos, httpx_mock): httpx_mock.add_response( json=load_fixture("connection.json"), @@ -75,6 +233,25 @@ def test_get_connection(self, workos, httpx_mock): assert request.method == "GET" assert request.url.path.endswith("/connections/test_id") + def test_update_connection(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("connection.json"), + ) + result = workos.sso.update_connection( + "test_id", + protocol_options=PatchProtocolOptionsSAML( + saml_options=PatchConnectionSAMLOptions.from_dict( + load_fixture("patch_connection_saml_options.json") + ) + ), + ) + assert isinstance(result, Connection) + assert result.object == "connection" + assert result.id == "conn_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "PATCH" + assert request.url.path.endswith("/connections/test_id") + def test_delete_connection(self, workos, httpx_mock): httpx_mock.add_response(status_code=200, content=b"\n") result = workos.sso.delete_connection("test_id") @@ -129,15 +306,13 @@ def test_get_profile_and_token(self, workos, httpx_mock): httpx_mock.add_response( json=load_fixture("sso_token_response.json"), ) - result = workos.sso.get_profile_and_token(code="test_code") + result = workos.sso.get_profile_and_token() assert isinstance(result, SSOTokenResponse) assert result.token_type == "Bearer" assert result.access_token == "eyJhbGciOiJSUzI1NiIsImtpZCI6InNzby..." request = httpx_mock.get_request() assert request.method == "POST" assert request.url.path.endswith("/sso/token") - body = json.loads(request.content) - assert body["code"] == "test_code" def test_list_connections_with_request_options(self, workos, httpx_mock): httpx_mock.add_response(json={"data": [], "list_metadata": {}}) @@ -256,6 +431,173 @@ async def test_list_connections_encodes_query_params( assert request.url.params["organization_id"] == "value organization_id/test" assert request.url.params["search"] == "value search/test" + @pytest.mark.asyncio + async def test_create_connection(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("connection.json")) + result = await async_workos.sso.create_connection( + organization_id="test_organization_id", + protocol_options=CreateProtocolOptionsSAML( + saml_options=CreateConnectionSAMLOptions.from_dict( + load_fixture("create_connection_saml_options.json") + ) + ), + ) + assert isinstance(result, Connection) + assert result.object == "connection" + assert result.id == "conn_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/connections") + + @pytest.mark.asyncio + async def test_list_connection_saml_idp_signing_certs( + self, async_workos, httpx_mock + ): + httpx_mock.add_response( + json=load_fixture("saml_idp_signing_certificate_list.json") + ) + result = await async_workos.sso.list_connection_saml_idp_signing_certs( + "test_connectionId" + ) + assert isinstance(result, SAMLIdpSigningCertificateList) + assert result.object == "list" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_idp_signing_certs" + ) + + @pytest.mark.asyncio + async def test_create_connection_saml_idp_signing_cert( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("saml_idp_signing_certificate.json")) + result = await async_workos.sso.create_connection_saml_idp_signing_cert( + "test_connectionId", value="test_value" + ) + assert isinstance(result, SAMLIdpSigningCertificate) + assert result.object == "saml_idp_signing_certificate" + assert result.id == "saml_x509_cert_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_idp_signing_certs" + ) + + @pytest.mark.asyncio + async def test_delete_connection_saml_idp_signing_cert( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(status_code=204) + result = await async_workos.sso.delete_connection_saml_idp_signing_cert( + "test_connectionId", "test_certificateId" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_idp_signing_certs/test_certificateId" + ) + + @pytest.mark.asyncio + async def test_list_connection_saml_sp_encryption_certs( + self, async_workos, httpx_mock + ): + httpx_mock.add_response( + json=load_fixture("saml_sp_encryption_certificate_list.json") + ) + result = await async_workos.sso.list_connection_saml_sp_encryption_certs( + "test_connectionId" + ) + assert isinstance(result, SAMLSpEncryptionCertificateList) + assert result.object == "list" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_encryption_certs" + ) + + @pytest.mark.asyncio + async def test_create_connection_saml_sp_encryption_cert( + self, async_workos, httpx_mock + ): + httpx_mock.add_response( + json=load_fixture("saml_sp_encryption_certificate.json") + ) + result = await async_workos.sso.create_connection_saml_sp_encryption_cert( + "test_connectionId" + ) + assert isinstance(result, SAMLSpEncryptionCertificate) + assert result.object == "saml_sp_encryption_certificate" + assert result.id == "saml_enc_key_pair_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_encryption_certs" + ) + + @pytest.mark.asyncio + async def test_delete_connection_saml_sp_encryption_cert( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(status_code=204) + result = await async_workos.sso.delete_connection_saml_sp_encryption_cert( + "test_connectionId", "test_certificateId" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_encryption_certs/test_certificateId" + ) + + @pytest.mark.asyncio + async def test_list_connection_saml_sp_signing_cert(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("saml_sp_signing_certificate.json")) + result = await async_workos.sso.list_connection_saml_sp_signing_cert( + "test_connectionId" + ) + assert isinstance(result, SAMLSpSigningCertificate) + assert result.object == "saml_sp_signing_certificate" + assert result.id == "saml_party_trust_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_signing_cert" + ) + + @pytest.mark.asyncio + async def test_create_connection_saml_sp_signing_cert( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("saml_sp_signing_certificate.json")) + result = await async_workos.sso.create_connection_saml_sp_signing_cert( + "test_connectionId" + ) + assert isinstance(result, SAMLSpSigningCertificate) + assert result.object == "saml_sp_signing_certificate" + assert result.id == "saml_party_trust_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_signing_cert" + ) + + @pytest.mark.asyncio + async def test_delete_connection_saml_sp_signing_cert( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(status_code=204) + result = await async_workos.sso.delete_connection_saml_sp_signing_cert( + "test_connectionId", "test_certificateId" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/connections/test_connectionId/saml_sp_signing_cert/test_certificateId" + ) + @pytest.mark.asyncio async def test_get_connection(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("connection.json")) @@ -267,6 +609,24 @@ async def test_get_connection(self, async_workos, httpx_mock): assert request.method == "GET" assert request.url.path.endswith("/connections/test_id") + @pytest.mark.asyncio + async def test_update_connection(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("connection.json")) + result = await async_workos.sso.update_connection( + "test_id", + protocol_options=PatchProtocolOptionsSAML( + saml_options=PatchConnectionSAMLOptions.from_dict( + load_fixture("patch_connection_saml_options.json") + ) + ), + ) + assert isinstance(result, Connection) + assert result.object == "connection" + assert result.id == "conn_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "PATCH" + assert request.url.path.endswith("/connections/test_id") + @pytest.mark.asyncio async def test_delete_connection(self, async_workos, httpx_mock): httpx_mock.add_response(status_code=200, content=b"\n") @@ -319,7 +679,7 @@ async def test_get_profile(self, async_workos, httpx_mock): @pytest.mark.asyncio async def test_get_profile_and_token(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("sso_token_response.json")) - result = await async_workos.sso.get_profile_and_token(code="test_code") + result = await async_workos.sso.get_profile_and_token() assert isinstance(result, SSOTokenResponse) assert result.token_type == "Bearer" assert result.access_token == "eyJhbGciOiJSUzI1NiIsImtpZCI6InNzby..." diff --git a/tests/test_sso_models_round_trip.py b/tests/test_sso_models_round_trip.py index 1af23692..faa404eb 100644 --- a/tests/test_sso_models_round_trip.py +++ b/tests/test_sso_models_round_trip.py @@ -6,7 +6,21 @@ from workos.sso.models import ( Connection, ConnectionDomain, + CreateConnectionAttributeMaps, + CreateConnectionKeyPair, + CreateConnectionOIDCOptions, + CreateConnectionSAMLOptions, + CreateConnectionStandardAttributes, + PatchConnectionAttributeMaps, + PatchConnectionOIDCOptions, + PatchConnectionSAMLOptions, + PatchConnectionStandardAttributes, Profile, + SAMLIdpSigningCertificate, + SAMLIdpSigningCertificateList, + SAMLSpEncryptionCertificate, + SAMLSpEncryptionCertificateList, + SAMLSpSigningCertificate, SSOAuthorizeUrlResponse, SSOLogoutAuthorizeResponse, SSOTokenResponse, @@ -15,6 +29,323 @@ class TestModelRoundTrip: + def test_create_connection_key_pair_round_trip(self): + data = load_fixture("create_connection_key_pair.json") + instance = CreateConnectionKeyPair.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = CreateConnectionKeyPair.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_create_connection_key_pair_minimal_payload(self): + data = { + "key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----", + "cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----", + } + instance = CreateConnectionKeyPair.from_dict(data) + serialized = instance.to_dict() + assert serialized["key"] == data["key"] + assert serialized["cert"] == data["cert"] + + def test_create_connection_saml_options_round_trip(self): + data = load_fixture("create_connection_saml_options.json") + instance = CreateConnectionSAMLOptions.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = CreateConnectionSAMLOptions.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_create_connection_saml_options_minimal_payload(self): + data = {} + instance = CreateConnectionSAMLOptions.from_dict(data) + assert instance.to_dict() is not None + + def test_create_connection_saml_options_omits_absent_optional_non_nullable_fields( + self, + ): + data = {} + instance = CreateConnectionSAMLOptions.from_dict(data) + serialized = instance.to_dict() + assert "idp_metadata_url" not in serialized + assert "acs_url" not in serialized + assert "sp_entity_id" not in serialized + assert "idp_entity_id" not in serialized + assert "idp_sso_url" not in serialized + assert "idp_signing_certs" not in serialized + assert "sp_signing_key_pair" not in serialized + assert "sp_encryption_key_pairs" not in serialized + + def test_create_connection_oidc_options_round_trip(self): + data = load_fixture("create_connection_oidc_options.json") + instance = CreateConnectionOIDCOptions.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = CreateConnectionOIDCOptions.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_create_connection_oidc_options_minimal_payload(self): + data = { + "discovery_endpoint": "https://idp.example.com/.well-known/openid-configuration", + "client_id": "client_123", + } + instance = CreateConnectionOIDCOptions.from_dict(data) + serialized = instance.to_dict() + assert serialized["discovery_endpoint"] == data["discovery_endpoint"] + assert serialized["client_id"] == data["client_id"] + + def test_create_connection_oidc_options_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "discovery_endpoint": "https://idp.example.com/.well-known/openid-configuration", + "client_id": "client_123", + } + instance = CreateConnectionOIDCOptions.from_dict(data) + serialized = instance.to_dict() + assert "client_secret" not in serialized + assert "redirect_uri" not in serialized + assert "pkce" not in serialized + assert "token_authentication_method" not in serialized + assert "jwt_signing_key_pair" not in serialized + assert "id_token_signature_algorithm" not in serialized + assert "fetch_user_info" not in serialized + + def test_create_connection_oidc_options_round_trips_unknown_enum_values(self): + data = { + "discovery_endpoint": "https://idp.example.com/.well-known/openid-configuration", + "client_id": "client_123", + "client_secret": "secret_xyz", + "redirect_uri": "https://auth.workos.com/sso/oidc/conn_externalkey/callback", + "pkce": True, + "token_authentication_method": "unexpected_create_connection_oidc_options_token_authentication_method", + "jwt_signing_key_pair": { + "key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----", + "cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----", + }, + "id_token_signature_algorithm": "RS256", + "fetch_user_info": False, + } + instance = CreateConnectionOIDCOptions.from_dict(data) + assert instance.to_dict() == data + + def test_create_connection_standard_attributes_round_trip(self): + data = load_fixture("create_connection_standard_attributes.json") + instance = CreateConnectionStandardAttributes.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = CreateConnectionStandardAttributes.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_create_connection_standard_attributes_minimal_payload(self): + data = {} + instance = CreateConnectionStandardAttributes.from_dict(data) + assert instance.to_dict() is not None + + def test_create_connection_standard_attributes_omits_absent_optional_non_nullable_fields( + self, + ): + data = {"groups": "memberOf", "name": "displayName"} + instance = CreateConnectionStandardAttributes.from_dict(data) + serialized = instance.to_dict() + assert "idp_id" not in serialized + assert "email" not in serialized + assert "first_name" not in serialized + assert "last_name" not in serialized + + def test_create_connection_standard_attributes_preserves_nullable_fields(self): + data = { + "idp_id": "sub", + "email": "email", + "first_name": "given_name", + "last_name": "family_name", + "groups": None, + "name": None, + } + instance = CreateConnectionStandardAttributes.from_dict(data) + serialized = instance.to_dict() + assert serialized["groups"] is None + assert serialized["name"] is None + + def test_create_connection_attribute_maps_round_trip(self): + data = load_fixture("create_connection_attribute_maps.json") + instance = CreateConnectionAttributeMaps.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = CreateConnectionAttributeMaps.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_create_connection_attribute_maps_minimal_payload(self): + data = {} + instance = CreateConnectionAttributeMaps.from_dict(data) + assert instance.to_dict() is not None + + def test_create_connection_attribute_maps_omits_absent_optional_non_nullable_fields( + self, + ): + data = {} + instance = CreateConnectionAttributeMaps.from_dict(data) + serialized = instance.to_dict() + assert "standard_attributes" not in serialized + assert "custom_attributes" not in serialized + + def test_patch_connection_saml_options_round_trip(self): + data = load_fixture("patch_connection_saml_options.json") + instance = PatchConnectionSAMLOptions.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PatchConnectionSAMLOptions.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_patch_connection_saml_options_minimal_payload(self): + data = {} + instance = PatchConnectionSAMLOptions.from_dict(data) + assert instance.to_dict() is not None + + def test_patch_connection_saml_options_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "idp_metadata_url": "https://idp.example.com/metadata.xml", + "acs_url": "https://example.auth0.com/login/callback?connection=123", + "sp_entity_id": "https://example.auth0.com/login/callback?connection=123", + } + instance = PatchConnectionSAMLOptions.from_dict(data) + serialized = instance.to_dict() + assert "idp_entity_id" not in serialized + assert "idp_sso_url" not in serialized + + def test_patch_connection_saml_options_preserves_nullable_fields(self): + data = { + "idp_metadata_url": None, + "acs_url": None, + "sp_entity_id": None, + "idp_entity_id": "https://idp.example.com/entity", + "idp_sso_url": "https://idp.example.com/sso", + } + instance = PatchConnectionSAMLOptions.from_dict(data) + serialized = instance.to_dict() + assert serialized["idp_metadata_url"] is None + assert serialized["acs_url"] is None + assert serialized["sp_entity_id"] is None + + def test_patch_connection_oidc_options_round_trip(self): + data = load_fixture("patch_connection_oidc_options.json") + instance = PatchConnectionOIDCOptions.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PatchConnectionOIDCOptions.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_patch_connection_oidc_options_minimal_payload(self): + data = {} + instance = PatchConnectionOIDCOptions.from_dict(data) + assert instance.to_dict() is not None + + def test_patch_connection_oidc_options_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "redirect_uri": "https://auth.workos.com/sso/oidc/conn_externalkey/callback" + } + instance = PatchConnectionOIDCOptions.from_dict(data) + serialized = instance.to_dict() + assert "discovery_endpoint" not in serialized + assert "client_id" not in serialized + assert "client_secret" not in serialized + assert "pkce" not in serialized + assert "token_authentication_method" not in serialized + assert "id_token_signature_algorithm" not in serialized + assert "fetch_user_info" not in serialized + + def test_patch_connection_oidc_options_preserves_nullable_fields(self): + data = { + "discovery_endpoint": "https://idp.example.com/.well-known/openid-configuration", + "client_id": "client_123", + "client_secret": "secret_xyz", + "redirect_uri": None, + "pkce": True, + "token_authentication_method": "client_secret_basic", + "id_token_signature_algorithm": "RS256", + "fetch_user_info": False, + } + instance = PatchConnectionOIDCOptions.from_dict(data) + serialized = instance.to_dict() + assert serialized["redirect_uri"] is None + + def test_patch_connection_oidc_options_round_trips_unknown_enum_values(self): + data = { + "discovery_endpoint": "https://idp.example.com/.well-known/openid-configuration", + "client_id": "client_123", + "client_secret": "secret_xyz", + "redirect_uri": "https://auth.workos.com/sso/oidc/conn_externalkey/callback", + "pkce": True, + "token_authentication_method": "unexpected_patch_connection_oidc_options_token_authentication_method", + "id_token_signature_algorithm": "RS256", + "fetch_user_info": False, + } + instance = PatchConnectionOIDCOptions.from_dict(data) + assert instance.to_dict() == data + + def test_patch_connection_standard_attributes_round_trip(self): + data = load_fixture("patch_connection_standard_attributes.json") + instance = PatchConnectionStandardAttributes.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PatchConnectionStandardAttributes.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_patch_connection_standard_attributes_minimal_payload(self): + data = {} + instance = PatchConnectionStandardAttributes.from_dict(data) + assert instance.to_dict() is not None + + def test_patch_connection_standard_attributes_omits_absent_optional_non_nullable_fields( + self, + ): + data = {"groups": "memberOf", "name": "displayName"} + instance = PatchConnectionStandardAttributes.from_dict(data) + serialized = instance.to_dict() + assert "idp_id" not in serialized + assert "email" not in serialized + assert "first_name" not in serialized + assert "last_name" not in serialized + + def test_patch_connection_standard_attributes_preserves_nullable_fields(self): + data = { + "idp_id": "sub", + "email": "email", + "first_name": "given_name", + "last_name": "family_name", + "groups": None, + "name": None, + } + instance = PatchConnectionStandardAttributes.from_dict(data) + serialized = instance.to_dict() + assert serialized["groups"] is None + assert serialized["name"] is None + + def test_patch_connection_attribute_maps_round_trip(self): + data = load_fixture("patch_connection_attribute_maps.json") + instance = PatchConnectionAttributeMaps.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PatchConnectionAttributeMaps.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_patch_connection_attribute_maps_minimal_payload(self): + data = {} + instance = PatchConnectionAttributeMaps.from_dict(data) + assert instance.to_dict() is not None + + def test_patch_connection_attribute_maps_omits_absent_optional_non_nullable_fields( + self, + ): + data = {} + instance = PatchConnectionAttributeMaps.from_dict(data) + serialized = instance.to_dict() + assert "standard_attributes" not in serialized + assert "custom_attributes" not in serialized + def test_connection_round_trip(self): data = load_fixture("connection.json") instance = Connection.from_dict(data) @@ -97,6 +428,180 @@ def test_connection_round_trips_unknown_enum_values(self): instance = Connection.from_dict(data) assert instance.to_dict() == data + def test_saml_idp_signing_certificate_round_trip(self): + data = load_fixture("saml_idp_signing_certificate.json") + instance = SAMLIdpSigningCertificate.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = SAMLIdpSigningCertificate.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_saml_idp_signing_certificate_minimal_payload(self): + data = { + "object": "saml_idp_signing_certificate", + "id": "saml_x509_cert_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": None, + "not_after": None, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = SAMLIdpSigningCertificate.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["value"] == data["value"] + assert serialized["not_before"] == data["not_before"] + assert serialized["not_after"] == data["not_after"] + assert serialized["created_at"] == data["created_at"] + + def test_saml_idp_signing_certificate_preserves_nullable_fields(self): + data = { + "object": "saml_idp_signing_certificate", + "id": "saml_x509_cert_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": None, + "not_after": None, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = SAMLIdpSigningCertificate.from_dict(data) + serialized = instance.to_dict() + assert serialized["not_before"] is None + assert serialized["not_after"] is None + + def test_saml_idp_signing_certificate_list_round_trip(self): + data = load_fixture("saml_idp_signing_certificate_list.json") + instance = SAMLIdpSigningCertificateList.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = SAMLIdpSigningCertificateList.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_saml_idp_signing_certificate_list_minimal_payload(self): + data = { + "object": "list", + "data": [ + { + "object": "saml_idp_signing_certificate", + "id": "saml_x509_cert_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": "2026-01-15T12:00:00.000Z", + "not_after": "2026-01-15T12:00:00.000Z", + "created_at": "2026-01-15T12:00:00.000Z", + } + ], + } + instance = SAMLIdpSigningCertificateList.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["data"] == data["data"] + + def test_saml_sp_encryption_certificate_round_trip(self): + data = load_fixture("saml_sp_encryption_certificate.json") + instance = SAMLSpEncryptionCertificate.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = SAMLSpEncryptionCertificate.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_saml_sp_encryption_certificate_minimal_payload(self): + data = { + "object": "saml_sp_encryption_certificate", + "id": "saml_enc_key_pair_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": None, + "not_after": None, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = SAMLSpEncryptionCertificate.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["value"] == data["value"] + assert serialized["not_before"] == data["not_before"] + assert serialized["not_after"] == data["not_after"] + assert serialized["created_at"] == data["created_at"] + + def test_saml_sp_encryption_certificate_preserves_nullable_fields(self): + data = { + "object": "saml_sp_encryption_certificate", + "id": "saml_enc_key_pair_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": None, + "not_after": None, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = SAMLSpEncryptionCertificate.from_dict(data) + serialized = instance.to_dict() + assert serialized["not_before"] is None + assert serialized["not_after"] is None + + def test_saml_sp_encryption_certificate_list_round_trip(self): + data = load_fixture("saml_sp_encryption_certificate_list.json") + instance = SAMLSpEncryptionCertificateList.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = SAMLSpEncryptionCertificateList.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_saml_sp_encryption_certificate_list_minimal_payload(self): + data = { + "object": "list", + "data": [ + { + "object": "saml_sp_encryption_certificate", + "id": "saml_enc_key_pair_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": "2026-01-15T12:00:00.000Z", + "not_after": "2026-01-15T12:00:00.000Z", + "created_at": "2026-01-15T12:00:00.000Z", + } + ], + } + instance = SAMLSpEncryptionCertificateList.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["data"] == data["data"] + + def test_saml_sp_signing_certificate_round_trip(self): + data = load_fixture("saml_sp_signing_certificate.json") + instance = SAMLSpSigningCertificate.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = SAMLSpSigningCertificate.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_saml_sp_signing_certificate_minimal_payload(self): + data = { + "object": "saml_sp_signing_certificate", + "id": "saml_party_trust_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": None, + "not_after": None, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = SAMLSpSigningCertificate.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["value"] == data["value"] + assert serialized["not_before"] == data["not_before"] + assert serialized["not_after"] == data["not_after"] + assert serialized["created_at"] == data["created_at"] + + def test_saml_sp_signing_certificate_preserves_nullable_fields(self): + data = { + "object": "saml_sp_signing_certificate", + "id": "saml_party_trust_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": None, + "not_after": None, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = SAMLSpSigningCertificate.from_dict(data) + serialized = instance.to_dict() + assert serialized["not_before"] is None + assert serialized["not_after"] is None + def test_sso_authorize_url_response_round_trip(self): data = load_fixture("sso_authorize_url_response.json") instance = SSOAuthorizeUrlResponse.from_dict(data) From c6e4d9adde9163af0c5427334ad4b9a65e2bbc1b Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:09:55 +0000 Subject: [PATCH 4/8] feat(user_management)!: SDK surface change: Parameter type changed for "body" on "UserManagement.create_authenticate" --- src/workos/user_management/_resource.py | 554 +++++++++++++++++- src/workos/user_management/models/__init__.py | 9 + .../models/create_password_reset_token.py | 31 +- .../models/create_waitlist_entry.py | 42 ++ .../models/user_management_waitlists_state.py | 30 + src/workos/user_management/models/waitlist.py | 45 ++ .../user_management/models/waitlist_entry.py | 78 +++ tests/test_user_management.py | 239 +++++++- .../test_user_management_models_round_trip.py | 99 ++++ 9 files changed, 1091 insertions(+), 36 deletions(-) create mode 100644 src/workos/user_management/models/create_waitlist_entry.py create mode 100644 src/workos/user_management/models/user_management_waitlists_state.py create mode 100644 src/workos/user_management/models/waitlist.py create mode 100644 src/workos/user_management/models/waitlist_entry.py diff --git a/src/workos/user_management/_resource.py b/src/workos/user_management/_resource.py index fb8bb9a3..53629ec7 100644 --- a/src/workos/user_management/_resource.py +++ b/src/workos/user_management/_resource.py @@ -42,6 +42,7 @@ DeviceCodeSessionAuthenticateRequest, EmailChange, EmailChangeConfirmation, + EmailCompletionSessionAuthenticateRequest, EmailVerification, EmailVerificationCodeSessionAuthenticateRequest, Invitation, @@ -69,7 +70,10 @@ UserInvite, UserManagementAuthenticationProvider, UserManagementAuthenticationScreenHint, + UserManagementWaitlistsState, VerifyEmailResponse, + Waitlist, + WaitlistEntry, ) @@ -134,6 +138,7 @@ def create_authenticate( | MagicAuthCodeSessionAuthenticateRequest | EmailVerificationCodeSessionAuthenticateRequest | MFATotpSessionAuthenticateRequest + | EmailCompletionSessionAuthenticateRequest | OrganizationSelectionSessionAuthenticateRequest | RadarEmailChallengeCodeSessionAuthenticateRequest | RadarSmsChallengeCodeSessionAuthenticateRequest @@ -146,7 +151,7 @@ def create_authenticate( Authenticate a user with a specified [authentication method](https://workos.com/docs/reference/authkit/authentication). Args: - body: The request body. Accepts: AuthorizationCodeSessionAuthenticateRequest, PasswordSessionAuthenticateRequest, RefreshTokenSessionAuthenticateRequest, MagicAuthCodeSessionAuthenticateRequest, EmailVerificationCodeSessionAuthenticateRequest, MFATotpSessionAuthenticateRequest, OrganizationSelectionSessionAuthenticateRequest, RadarEmailChallengeCodeSessionAuthenticateRequest, RadarSmsChallengeCodeSessionAuthenticateRequest, DeviceCodeSessionAuthenticateRequest, or a plain dict. + body: The request body. Accepts: AuthorizationCodeSessionAuthenticateRequest, PasswordSessionAuthenticateRequest, RefreshTokenSessionAuthenticateRequest, MagicAuthCodeSessionAuthenticateRequest, EmailVerificationCodeSessionAuthenticateRequest, MFATotpSessionAuthenticateRequest, EmailCompletionSessionAuthenticateRequest, OrganizationSelectionSessionAuthenticateRequest, RadarEmailChallengeCodeSessionAuthenticateRequest, RadarSmsChallengeCodeSessionAuthenticateRequest, DeviceCodeSessionAuthenticateRequest, or a plain dict. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1324,7 +1329,6 @@ def delete_user( Raises: NotFoundError: If the resource is not found (404). - ConflictError: If a conflict occurs (409). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -2169,6 +2173,277 @@ def delete_user_authorized_application( request_options=request_options, ) + def delete_waitlist_entry( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete a waitlist entry + + Remove the entry from the waitlist. Its email address can join again unless a user with that email now exists in the environment. Deleting the entry does not revoke an invitation created by approving it — [revoke that invitation](https://workos.com/docs/reference/authkit/invitation/revoke) separately to withdraw access. + + Args: + id: The unique ID of the waitlist entry. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="delete", + path=("user_management", "waitlist_entries", str(id)), + request_options=request_options, + ) + + def create_waitlist_entry_approve( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> WaitlistEntry: + """Approve a waitlist entry + + Approve a waitlist entry, create an invitation for its email address, and send the invitation email. Approving a denied entry reverses the denial. The approval is saved even when the invitation steps fail, so instead of retrying the approval, recover based on the outcome: + + - `200` — the entry is approved. If invitation creation failed, no invitation exists yet; [send](https://workos.com/docs/reference/authkit/invitation/send) one. + - `422` with code `invitation_email_not_sent` — the entry is approved and an invitation exists, but its email was not sent; [resend](https://workos.com/docs/reference/authkit/invitation/resend) it. + - `422` with code `invalid_state` — the entry was already approved. + + Args: + id: The unique ID of the waitlist entry. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + WaitlistEntry + + Raises: + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="post", + path=("user_management", "waitlist_entries", str(id), "approve"), + model=WaitlistEntry, + request_options=request_options, + ) + + def create_waitlist_entry_deny( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> WaitlistEntry: + """Deny a waitlist entry + + Deny a pending waitlist entry. Denying an entry that is not pending fails with the code `invalid_state`. A denial can be reversed by approving the entry. + + Args: + id: The unique ID of the waitlist entry. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + WaitlistEntry + + Raises: + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="post", + path=("user_management", "waitlist_entries", str(id), "deny"), + model=WaitlistEntry, + request_options=request_options, + ) + + def list_waitlists( + self, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: str | None = None, + request_options: RequestOptions | None = None, + ) -> SyncPage[Waitlist]: + """List waitlists + + Get a list of the waitlists in the environment. All waitlists are returned in a single response — this endpoint is not paginated, so the `list_metadata` cursors are always `null`. + + Args: + limit: The limit. + before: The before. + after: The after. + order: The order. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SyncPage[Waitlist] + + Raises: + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": order, + }.items() + if v is not None + } + return self._client.request_page( + method="get", + path=("user_management", "waitlists"), + model=Waitlist, + params=params, + request_options=request_options, + ) + + def get_waitlist( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> Waitlist: + """Get a waitlist + + Get the details of an existing waitlist. + + Args: + id: The unique ID of the waitlist, or the literal `default` for the environment's default waitlist. The default waitlist is created when its first entry is added, so read requests for `default` return a `404` until then. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + Waitlist + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("user_management", "waitlists", str(id)), + model=Waitlist, + request_options=request_options, + ) + + def list_waitlist_entries( + self, + id: str, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", + state: UserManagementWaitlistsState | str | None = None, + email: str | None = None, + request_options: RequestOptions | None = None, + ) -> SyncPage[WaitlistEntry]: + """List waitlist entries + + Get a list of entries on a waitlist matching the criteria specified. + + Args: + id: The unique ID of the waitlist, or the literal `default` for the environment's default waitlist. The default waitlist is created when its first entry is added, so read requests for `default` return a `404` until then. + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + state: Filter waitlist entries by their state. + email: Filter waitlist entries by their exact email address. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SyncPage[WaitlistEntry] + + Raises: + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + "state": enum_value(state) if state is not None else None, + "email": email, + }.items() + if v is not None + } + return self._client.request_page( + method="get", + path=("user_management", "waitlists", str(id), "entries"), + model=WaitlistEntry, + params=params, + request_options=request_options, + ) + + def create_waitlist_entry( + self, + id: str, + *, + email: str, + additional_fields: dict[str, str] | None = None, + send_confirmation_email: bool | None = None, + request_options: RequestOptions | None = None, + ) -> WaitlistEntry: + """Create a waitlist entry + + Add an email address to the waitlist. Email addresses are normalized and unique per environment: a request for an email address already on the waitlist returns the existing entry unchanged (still with status `201`) and does not send another confirmation email. If a user with the email address already exists in the environment, the request fails with the code `user_already_exists`. + + Args: + id: The unique ID of the waitlist, or the literal `default` for the environment's default waitlist. Use `default` when adding the first entry — the default waitlist is created automatically. + email: The email address of the user joining the waitlist. + additional_fields: Object containing additional key/value pairs collected with the waitlist entry. Supports up to 50 string pairs, with keys up to 40 characters and values up to 600 characters. Values are user-provided — treat them as untrusted input when rendering or exporting. + send_confirmation_email: Whether to send the waitlist confirmation email to the user. Defaults to `false`. No email is sent when the waitlist confirmation email is disabled in the environment, even if `send_confirmation_email` is `true`. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + WaitlistEntry + + Raises: + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "email": email, + "additional_fields": additional_fields, + "send_confirmation_email": send_confirmation_email, + }.items() + if v is not None + } + return self._client.request( + method="post", + path=("user_management", "waitlists", str(id), "entries"), + body=body, + model=WaitlistEntry, + request_options=request_options, + ) + def list_user_api_keys( self, user_id: str, @@ -2502,6 +2777,7 @@ async def create_authenticate( | MagicAuthCodeSessionAuthenticateRequest | EmailVerificationCodeSessionAuthenticateRequest | MFATotpSessionAuthenticateRequest + | EmailCompletionSessionAuthenticateRequest | OrganizationSelectionSessionAuthenticateRequest | RadarEmailChallengeCodeSessionAuthenticateRequest | RadarSmsChallengeCodeSessionAuthenticateRequest @@ -2514,7 +2790,7 @@ async def create_authenticate( Authenticate a user with a specified [authentication method](https://workos.com/docs/reference/authkit/authentication). Args: - body: The request body. Accepts: AuthorizationCodeSessionAuthenticateRequest, PasswordSessionAuthenticateRequest, RefreshTokenSessionAuthenticateRequest, MagicAuthCodeSessionAuthenticateRequest, EmailVerificationCodeSessionAuthenticateRequest, MFATotpSessionAuthenticateRequest, OrganizationSelectionSessionAuthenticateRequest, RadarEmailChallengeCodeSessionAuthenticateRequest, RadarSmsChallengeCodeSessionAuthenticateRequest, DeviceCodeSessionAuthenticateRequest, or a plain dict. + body: The request body. Accepts: AuthorizationCodeSessionAuthenticateRequest, PasswordSessionAuthenticateRequest, RefreshTokenSessionAuthenticateRequest, MagicAuthCodeSessionAuthenticateRequest, EmailVerificationCodeSessionAuthenticateRequest, MFATotpSessionAuthenticateRequest, EmailCompletionSessionAuthenticateRequest, OrganizationSelectionSessionAuthenticateRequest, RadarEmailChallengeCodeSessionAuthenticateRequest, RadarSmsChallengeCodeSessionAuthenticateRequest, DeviceCodeSessionAuthenticateRequest, or a plain dict. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -3692,7 +3968,6 @@ async def delete_user( Raises: NotFoundError: If the resource is not found (404). - ConflictError: If a conflict occurs (409). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -4537,6 +4812,277 @@ async def delete_user_authorized_application( request_options=request_options, ) + async def delete_waitlist_entry( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete a waitlist entry + + Remove the entry from the waitlist. Its email address can join again unless a user with that email now exists in the environment. Deleting the entry does not revoke an invitation created by approving it — [revoke that invitation](https://workos.com/docs/reference/authkit/invitation/revoke) separately to withdraw access. + + Args: + id: The unique ID of the waitlist entry. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + await self._client.request( + method="delete", + path=("user_management", "waitlist_entries", str(id)), + request_options=request_options, + ) + + async def create_waitlist_entry_approve( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> WaitlistEntry: + """Approve a waitlist entry + + Approve a waitlist entry, create an invitation for its email address, and send the invitation email. Approving a denied entry reverses the denial. The approval is saved even when the invitation steps fail, so instead of retrying the approval, recover based on the outcome: + + - `200` — the entry is approved. If invitation creation failed, no invitation exists yet; [send](https://workos.com/docs/reference/authkit/invitation/send) one. + - `422` with code `invitation_email_not_sent` — the entry is approved and an invitation exists, but its email was not sent; [resend](https://workos.com/docs/reference/authkit/invitation/resend) it. + - `422` with code `invalid_state` — the entry was already approved. + + Args: + id: The unique ID of the waitlist entry. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + WaitlistEntry + + Raises: + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="post", + path=("user_management", "waitlist_entries", str(id), "approve"), + model=WaitlistEntry, + request_options=request_options, + ) + + async def create_waitlist_entry_deny( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> WaitlistEntry: + """Deny a waitlist entry + + Deny a pending waitlist entry. Denying an entry that is not pending fails with the code `invalid_state`. A denial can be reversed by approving the entry. + + Args: + id: The unique ID of the waitlist entry. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + WaitlistEntry + + Raises: + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="post", + path=("user_management", "waitlist_entries", str(id), "deny"), + model=WaitlistEntry, + request_options=request_options, + ) + + async def list_waitlists( + self, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: str | None = None, + request_options: RequestOptions | None = None, + ) -> AsyncPage[Waitlist]: + """List waitlists + + Get a list of the waitlists in the environment. All waitlists are returned in a single response — this endpoint is not paginated, so the `list_metadata` cursors are always `null`. + + Args: + limit: The limit. + before: The before. + after: The after. + order: The order. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AsyncPage[Waitlist] + + Raises: + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": order, + }.items() + if v is not None + } + return await self._client.request_page( + method="get", + path=("user_management", "waitlists"), + model=Waitlist, + params=params, + request_options=request_options, + ) + + async def get_waitlist( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> Waitlist: + """Get a waitlist + + Get the details of an existing waitlist. + + Args: + id: The unique ID of the waitlist, or the literal `default` for the environment's default waitlist. The default waitlist is created when its first entry is added, so read requests for `default` return a `404` until then. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + Waitlist + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("user_management", "waitlists", str(id)), + model=Waitlist, + request_options=request_options, + ) + + async def list_waitlist_entries( + self, + id: str, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", + state: UserManagementWaitlistsState | str | None = None, + email: str | None = None, + request_options: RequestOptions | None = None, + ) -> AsyncPage[WaitlistEntry]: + """List waitlist entries + + Get a list of entries on a waitlist matching the criteria specified. + + Args: + id: The unique ID of the waitlist, or the literal `default` for the environment's default waitlist. The default waitlist is created when its first entry is added, so read requests for `default` return a `404` until then. + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + state: Filter waitlist entries by their state. + email: Filter waitlist entries by their exact email address. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AsyncPage[WaitlistEntry] + + Raises: + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + "state": enum_value(state) if state is not None else None, + "email": email, + }.items() + if v is not None + } + return await self._client.request_page( + method="get", + path=("user_management", "waitlists", str(id), "entries"), + model=WaitlistEntry, + params=params, + request_options=request_options, + ) + + async def create_waitlist_entry( + self, + id: str, + *, + email: str, + additional_fields: dict[str, str] | None = None, + send_confirmation_email: bool | None = None, + request_options: RequestOptions | None = None, + ) -> WaitlistEntry: + """Create a waitlist entry + + Add an email address to the waitlist. Email addresses are normalized and unique per environment: a request for an email address already on the waitlist returns the existing entry unchanged (still with status `201`) and does not send another confirmation email. If a user with the email address already exists in the environment, the request fails with the code `user_already_exists`. + + Args: + id: The unique ID of the waitlist, or the literal `default` for the environment's default waitlist. Use `default` when adding the first entry — the default waitlist is created automatically. + email: The email address of the user joining the waitlist. + additional_fields: Object containing additional key/value pairs collected with the waitlist entry. Supports up to 50 string pairs, with keys up to 40 characters and values up to 600 characters. Values are user-provided — treat them as untrusted input when rendering or exporting. + send_confirmation_email: Whether to send the waitlist confirmation email to the user. Defaults to `false`. No email is sent when the waitlist confirmation email is disabled in the environment, even if `send_confirmation_email` is `true`. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + WaitlistEntry + + Raises: + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "email": email, + "additional_fields": additional_fields, + "send_confirmation_email": send_confirmation_email, + }.items() + if v is not None + } + return await self._client.request( + method="post", + path=("user_management", "waitlists", str(id), "entries"), + body=body, + model=WaitlistEntry, + request_options=request_options, + ) + async def list_user_api_keys( self, user_id: str, diff --git a/src/workos/user_management/models/__init__.py b/src/workos/user_management/models/__init__.py index 141601d9..b5f36473 100644 --- a/src/workos/user_management/models/__init__.py +++ b/src/workos/user_management/models/__init__.py @@ -37,6 +37,7 @@ from .create_user_invite_options import ( CreateUserInviteOptions as CreateUserInviteOptions, ) +from .create_waitlist_entry import CreateWaitlistEntry as CreateWaitlistEntry from .device_authorization_response import ( DeviceAuthorizationResponse as DeviceAuthorizationResponse, ) @@ -50,6 +51,9 @@ from .email_change_confirmation_user import ( EmailChangeConfirmationUser as EmailChangeConfirmationUser, ) +from .email_completion_session_authenticate_request import ( + EmailCompletionSessionAuthenticateRequest as EmailCompletionSessionAuthenticateRequest, +) from .email_verification import EmailVerification as EmailVerification from .email_verification_code_session_authenticate_request import ( EmailVerificationCodeSessionAuthenticateRequest as EmailVerificationCodeSessionAuthenticateRequest, @@ -119,5 +123,10 @@ from .user_management_authentication_screen_hint import ( UserManagementAuthenticationScreenHint as UserManagementAuthenticationScreenHint, ) +from .user_management_waitlists_state import ( + UserManagementWaitlistsState as UserManagementWaitlistsState, +) from .verify_email_address import VerifyEmailAddress as VerifyEmailAddress from .verify_email_response import VerifyEmailResponse as VerifyEmailResponse +from .waitlist import Waitlist as Waitlist +from .waitlist_entry import WaitlistEntry as WaitlistEntry diff --git a/src/workos/user_management/models/create_password_reset_token.py b/src/workos/user_management/models/create_password_reset_token.py index 2a49b972..7e8f7791 100644 --- a/src/workos/user_management/models/create_password_reset_token.py +++ b/src/workos/user_management/models/create_password_reset_token.py @@ -1,32 +1,7 @@ # This file is auto-generated by oagen. Do not edit. -from __future__ import annotations +from typing import TypeAlias -from dataclasses import dataclass -from typing import Any +from workos.organizations.models.create_it_contact import CreateItContact -from workos._types import _raise_deserialize_error - - -@dataclass(slots=True) -class CreatePasswordResetToken: - """Create Password Reset Token model.""" - - email: str - """The email address of the user requesting a password reset.""" - - @classmethod - def from_dict(cls, data: dict[str, Any]) -> CreatePasswordResetToken: - """Deserialize from a dictionary.""" - try: - return cls( - email=data["email"], - ) - except (KeyError, ValueError) as e: - _raise_deserialize_error("CreatePasswordResetToken", e) - - def to_dict(self) -> dict[str, Any]: - """Serialize to a dictionary.""" - result: dict[str, Any] = {} - result["email"] = self.email - return result +CreatePasswordResetToken: TypeAlias = CreateItContact diff --git a/src/workos/user_management/models/create_waitlist_entry.py b/src/workos/user_management/models/create_waitlist_entry.py new file mode 100644 index 00000000..015f21ed --- /dev/null +++ b/src/workos/user_management/models/create_waitlist_entry.py @@ -0,0 +1,42 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class CreateWaitlistEntry: + """Create Waitlist Entry model.""" + + email: str + """The email address of the user joining the waitlist.""" + additional_fields: dict[str, str] | None = None + """Object containing additional key/value pairs collected with the waitlist entry. Supports up to 50 string pairs, with keys up to 40 characters and values up to 600 characters. Values are user-provided — treat them as untrusted input when rendering or exporting.""" + send_confirmation_email: bool | None = None + """Whether to send the waitlist confirmation email to the user. Defaults to `false`. No email is sent when the waitlist confirmation email is disabled in the environment, even if `send_confirmation_email` is `true`.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateWaitlistEntry: + """Deserialize from a dictionary.""" + try: + return cls( + email=data["email"], + additional_fields=data.get("additional_fields"), + send_confirmation_email=data.get("send_confirmation_email"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateWaitlistEntry", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["email"] = self.email + if self.additional_fields is not None: + result["additional_fields"] = self.additional_fields + if self.send_confirmation_email is not None: + result["send_confirmation_email"] = self.send_confirmation_email + return result diff --git a/src/workos/user_management/models/user_management_waitlists_state.py b/src/workos/user_management/models/user_management_waitlists_state.py new file mode 100644 index 00000000..48866c9e --- /dev/null +++ b/src/workos/user_management/models/user_management_waitlists_state.py @@ -0,0 +1,30 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of user management waitlists state values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class UserManagementWaitlistsState(str, Enum): + """Known values for UserManagementWaitlistsState.""" + + PENDING = "pending" + APPROVED = "approved" + DENIED = "denied" + + @classmethod + def _missing_(cls, value: object) -> UserManagementWaitlistsState | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +UserManagementWaitlistsStateLiteral: TypeAlias = Literal[ + "pending", "approved", "denied" +] diff --git a/src/workos/user_management/models/waitlist.py b/src/workos/user_management/models/waitlist.py new file mode 100644 index 00000000..6e77cb71 --- /dev/null +++ b/src/workos/user_management/models/waitlist.py @@ -0,0 +1,45 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + + +@dataclass(slots=True) +class Waitlist: + """Waitlist model.""" + + object: Literal["waitlist"] + """Distinguishes the Waitlist object.""" + id: str + """The unique ID of the Waitlist.""" + created_at: datetime + """An ISO 8601 timestamp.""" + updated_at: datetime + """An ISO 8601 timestamp.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Waitlist: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "waitlist"), + id=data["id"], + created_at=_parse_datetime(data["created_at"]), + updated_at=_parse_datetime(data["updated_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("Waitlist", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["created_at"] = _format_datetime(self.created_at) + result["updated_at"] = _format_datetime(self.updated_at) + return result diff --git a/src/workos/user_management/models/waitlist_entry.py b/src/workos/user_management/models/waitlist_entry.py new file mode 100644 index 00000000..2fd60494 --- /dev/null +++ b/src/workos/user_management/models/waitlist_entry.py @@ -0,0 +1,78 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from enum import Enum +from typing import Any, Literal + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error +from workos.common.models.waitlist_entry_state import WaitlistEntryState + + +@dataclass(slots=True) +class WaitlistEntry: + """Waitlist Entry model.""" + + id: str + """The unique ID of the waitlist entry.""" + email: str + """The email address of the user on the waitlist.""" + state: WaitlistEntryState + """The state of the waitlist entry.""" + approved_at: datetime | None + """The timestamp when the entry was approved, or null if not yet approved.""" + created_at: datetime + """An ISO 8601 timestamp.""" + updated_at: datetime + """An ISO 8601 timestamp.""" + object: Literal["waitlist_entry"] + """Distinguishes the Waitlist Entry object.""" + additional_fields: dict[str, str] | None = None + """Additional fields submitted when the user joined the waitlist. Values are user-provided — treat them as untrusted input when rendering or exporting.""" + waitlist_id: str | None = None + """The unique ID of the waitlist the entry belongs to.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> WaitlistEntry: + """Deserialize from a dictionary.""" + try: + return cls( + id=data["id"], + email=data["email"], + state=WaitlistEntryState(data["state"]), + approved_at=_parse_datetime(_v_approved_at) + if (_v_approved_at := data["approved_at"]) is not None + else None, + created_at=_parse_datetime(data["created_at"]), + updated_at=_parse_datetime(data["updated_at"]), + object=data.get("object", "waitlist_entry"), + additional_fields=data.get("additional_fields"), + waitlist_id=data.get("waitlist_id"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("WaitlistEntry", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["id"] = self.id + result["email"] = self.email + result["state"] = ( + self.state.value if isinstance(self.state, Enum) else self.state + ) + if self.approved_at is not None: + result["approved_at"] = _format_datetime(self.approved_at) + else: + result["approved_at"] = None + result["created_at"] = _format_datetime(self.created_at) + result["updated_at"] = _format_datetime(self.updated_at) + result["object"] = self.object + if self.additional_fields is not None: + result["additional_fields"] = self.additional_fields + if self.waitlist_id is not None: + result["waitlist_id"] = self.waitlist_id + else: + result["waitlist_id"] = None + return result diff --git a/tests/test_user_management.py b/tests/test_user_management.py index 21109f21..3a76684d 100644 --- a/tests/test_user_management.py +++ b/tests/test_user_management.py @@ -45,7 +45,10 @@ UserCreateResponse, UserIdentitiesGetItem, UserInvite, + UserManagementWaitlistsState, VerifyEmailResponse, + Waitlist, + WaitlistEntry, ) @@ -280,7 +283,7 @@ def test_create_user(self, workos, httpx_mock): json=load_fixture("user_create_response.json"), ) result = workos.user_management.create_user( - email="test_email", password=PasswordPlaintext(password="test_value") + email="test_email", password=PasswordPlaintext(password="test_password") ) assert isinstance(result, UserCreateResponse) assert result.object == "user" @@ -322,7 +325,7 @@ def test_update_user(self, workos, httpx_mock): json=load_fixture("user.json"), ) result = workos.user_management.update_user( - "test_id", password=PasswordPlaintext(password="test_value") + "test_id", password=PasswordPlaintext(password="test_password") ) assert isinstance(result, User) assert result.object == "user" @@ -695,6 +698,119 @@ def test_delete_user_authorized_application(self, workos, httpx_mock): "/user_management/users/test_user_id/authorized_applications/test_application_id" ) + def test_delete_waitlist_entry(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.user_management.delete_waitlist_entry("test_id") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/user_management/waitlist_entries/test_id") + + def test_create_waitlist_entry_approve(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("waitlist_entry.json"), + ) + result = workos.user_management.create_waitlist_entry_approve("test_id") + assert isinstance(result, WaitlistEntry) + assert result.id == "wl_user_01E4ZCR3C56J083X43JQXF3JK5" + assert result.email == "marcelina.davis@example.com" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/user_management/waitlist_entries/test_id/approve" + ) + + def test_create_waitlist_entry_deny(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("waitlist_entry.json"), + ) + result = workos.user_management.create_waitlist_entry_deny("test_id") + assert isinstance(result, WaitlistEntry) + assert result.id == "wl_user_01E4ZCR3C56J083X43JQXF3JK5" + assert result.email == "marcelina.davis@example.com" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/user_management/waitlist_entries/test_id/deny" + ) + + def test_list_waitlists(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("list_waitlist.json"), + ) + page = workos.user_management.list_waitlists() + assert isinstance(page, SyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], Waitlist) + + def test_list_waitlists_empty_page(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = workos.user_management.list_waitlists() + assert isinstance(page, SyncPage) + assert page.data == [] + + def test_get_waitlist(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("waitlist.json"), + ) + result = workos.user_management.get_waitlist("test_id") + assert isinstance(result, Waitlist) + assert result.object == "waitlist" + assert result.id == "waitlist_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/user_management/waitlists/test_id") + + def test_list_waitlist_entries(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("list_waitlist_entry.json"), + ) + page = workos.user_management.list_waitlist_entries("test_id") + assert isinstance(page, SyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], WaitlistEntry) + + def test_list_waitlist_entries_empty_page(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = workos.user_management.list_waitlist_entries("test_id") + assert isinstance(page, SyncPage) + assert page.data == [] + + def test_list_waitlist_entries_encodes_query_params(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.user_management.list_waitlist_entries( + "test_id", + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + state=UserManagementWaitlistsState("pending"), + email="value email/test", + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + assert request.url.params["state"] == "pending" + assert request.url.params["email"] == "value email/test" + + def test_create_waitlist_entry(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("waitlist_entry.json"), + ) + result = workos.user_management.create_waitlist_entry( + "test_id", email="test_email" + ) + assert isinstance(result, WaitlistEntry) + assert result.id == "wl_user_01E4ZCR3C56J083X43JQXF3JK5" + assert result.email == "marcelina.davis@example.com" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/user_management/waitlists/test_id/entries") + body = json.loads(request.content) + assert body["email"] == "test_email" + def test_list_user_api_keys(self, workos, httpx_mock): httpx_mock.add_response( json=load_fixture("list_user_api_key.json"), @@ -1162,7 +1278,7 @@ async def test_list_users_encodes_query_params(self, async_workos, httpx_mock): async def test_create_user(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("user_create_response.json")) result = await async_workos.user_management.create_user( - email="test_email", password=PasswordPlaintext(password="test_value") + email="test_email", password=PasswordPlaintext(password="test_password") ) assert isinstance(result, UserCreateResponse) assert result.object == "user" @@ -1201,7 +1317,7 @@ async def test_get_user(self, async_workos, httpx_mock): async def test_update_user(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("user.json")) result = await async_workos.user_management.update_user( - "test_id", password=PasswordPlaintext(password="test_value") + "test_id", password=PasswordPlaintext(password="test_password") ) assert isinstance(result, User) assert result.object == "user" @@ -1577,6 +1693,121 @@ async def test_delete_user_authorized_application(self, async_workos, httpx_mock "/user_management/users/test_user_id/authorized_applications/test_application_id" ) + @pytest.mark.asyncio + async def test_delete_waitlist_entry(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = await async_workos.user_management.delete_waitlist_entry("test_id") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/user_management/waitlist_entries/test_id") + + @pytest.mark.asyncio + async def test_create_waitlist_entry_approve(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("waitlist_entry.json")) + result = await async_workos.user_management.create_waitlist_entry_approve( + "test_id" + ) + assert isinstance(result, WaitlistEntry) + assert result.id == "wl_user_01E4ZCR3C56J083X43JQXF3JK5" + assert result.email == "marcelina.davis@example.com" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/user_management/waitlist_entries/test_id/approve" + ) + + @pytest.mark.asyncio + async def test_create_waitlist_entry_deny(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("waitlist_entry.json")) + result = await async_workos.user_management.create_waitlist_entry_deny( + "test_id" + ) + assert isinstance(result, WaitlistEntry) + assert result.id == "wl_user_01E4ZCR3C56J083X43JQXF3JK5" + assert result.email == "marcelina.davis@example.com" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/user_management/waitlist_entries/test_id/deny" + ) + + @pytest.mark.asyncio + async def test_list_waitlists(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("list_waitlist.json")) + page = await async_workos.user_management.list_waitlists() + assert isinstance(page, AsyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], Waitlist) + + @pytest.mark.asyncio + async def test_list_waitlists_empty_page(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = await async_workos.user_management.list_waitlists() + assert isinstance(page, AsyncPage) + assert page.data == [] + + @pytest.mark.asyncio + async def test_get_waitlist(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("waitlist.json")) + result = await async_workos.user_management.get_waitlist("test_id") + assert isinstance(result, Waitlist) + assert result.object == "waitlist" + assert result.id == "waitlist_01E4ZCR3C56J083X43JQXF3JK5" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/user_management/waitlists/test_id") + + @pytest.mark.asyncio + async def test_list_waitlist_entries(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("list_waitlist_entry.json")) + page = await async_workos.user_management.list_waitlist_entries("test_id") + assert isinstance(page, AsyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], WaitlistEntry) + + @pytest.mark.asyncio + async def test_list_waitlist_entries_empty_page(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = await async_workos.user_management.list_waitlist_entries("test_id") + assert isinstance(page, AsyncPage) + assert page.data == [] + + @pytest.mark.asyncio + async def test_list_waitlist_entries_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.user_management.list_waitlist_entries( + "test_id", + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + state=UserManagementWaitlistsState("pending"), + email="value email/test", + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + assert request.url.params["state"] == "pending" + assert request.url.params["email"] == "value email/test" + + @pytest.mark.asyncio + async def test_create_waitlist_entry(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("waitlist_entry.json")) + result = await async_workos.user_management.create_waitlist_entry( + "test_id", email="test_email" + ) + assert isinstance(result, WaitlistEntry) + assert result.id == "wl_user_01E4ZCR3C56J083X43JQXF3JK5" + assert result.email == "marcelina.davis@example.com" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/user_management/waitlists/test_id/entries") + @pytest.mark.asyncio async def test_list_user_api_keys(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("list_user_api_key.json")) diff --git a/tests/test_user_management_models_round_trip.py b/tests/test_user_management_models_round_trip.py index 518299b7..07a63f68 100644 --- a/tests/test_user_management_models_round_trip.py +++ b/tests/test_user_management_models_round_trip.py @@ -37,6 +37,8 @@ UserSessionsImpersonator, UserSessionsListItem, VerifyEmailResponse, + Waitlist, + WaitlistEntry, ) @@ -799,6 +801,103 @@ def test_device_authorization_response_omits_absent_optional_non_nullable_fields assert "verification_uri_complete" not in serialized assert "interval" not in serialized + def test_waitlist_round_trip(self): + data = load_fixture("waitlist.json") + instance = Waitlist.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = Waitlist.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_waitlist_minimal_payload(self): + data = { + "object": "waitlist", + "id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = Waitlist.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_waitlist_entry_round_trip(self): + data = load_fixture("waitlist_entry.json") + instance = WaitlistEntry.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = WaitlistEntry.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_waitlist_entry_minimal_payload(self): + data = { + "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", + "email": "marcelina.davis@example.com", + "state": "pending", + "approved_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_entry", + } + instance = WaitlistEntry.from_dict(data) + serialized = instance.to_dict() + assert serialized["id"] == data["id"] + assert serialized["email"] == data["email"] + assert serialized["state"] == data["state"] + assert serialized["approved_at"] == data["approved_at"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + assert serialized["object"] == data["object"] + + def test_waitlist_entry_omits_absent_optional_non_nullable_fields(self): + data = { + "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", + "email": "marcelina.davis@example.com", + "state": "pending", + "approved_at": None, + "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_entry", + } + instance = WaitlistEntry.from_dict(data) + serialized = instance.to_dict() + assert "additional_fields" not in serialized + + def test_waitlist_entry_preserves_nullable_fields(self): + data = { + "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", + "email": "marcelina.davis@example.com", + "state": "pending", + "approved_at": None, + "additional_fields": {"company": "Example Corp"}, + "waitlist_id": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_entry", + } + instance = WaitlistEntry.from_dict(data) + serialized = instance.to_dict() + assert serialized["approved_at"] is None + assert serialized["waitlist_id"] is None + + def test_waitlist_entry_round_trips_unknown_enum_values(self): + data = { + "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", + "email": "marcelina.davis@example.com", + "state": "unexpected_waitlist_entry_state", + "approved_at": None, + "additional_fields": {"company": "Example Corp"}, + "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_entry", + } + instance = WaitlistEntry.from_dict(data) + assert instance.to_dict() == data + def test_jwks_response_round_trip(self): data = load_fixture("jwks_response.json") instance = JwksResponse.from_dict(data) From 59e2610c0a8a37f88751ea8798821210491df8f2 Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:09:55 +0000 Subject: [PATCH 5/8] feat(user_management): Add model `EmailCompletionSessionAuthenticateRequest` --- ...completion_session_authenticate_request.py | 71 +++++++++++++++++++ ...mpletion_session_authenticate_request.json | 11 +++ 2 files changed, 82 insertions(+) create mode 100644 src/workos/user_management/models/email_completion_session_authenticate_request.py create mode 100644 tests/fixtures/email_completion_session_authenticate_request.json diff --git a/src/workos/user_management/models/email_completion_session_authenticate_request.py b/src/workos/user_management/models/email_completion_session_authenticate_request.py new file mode 100644 index 00000000..a25db622 --- /dev/null +++ b/src/workos/user_management/models/email_completion_session_authenticate_request.py @@ -0,0 +1,71 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class EmailCompletionSessionAuthenticateRequest: + """Email Completion Session Authenticate Request model.""" + + client_id: str + """The client ID of the application.""" + client_secret: str + """The client secret of the application.""" + grant_type: Literal["urn:workos:oauth:grant-type:email-completion"] + email_completion_token: str + """The token from the `email_completion_required` response.""" + email: str + """The email address the user supplied for a profile the identity provider left without one.""" + ip_address: str | None = None + """The IP address of the user's request.""" + device_id: str | None = None + """A unique identifier for the device.""" + user_agent: str | None = None + """The user agent string from the user's browser.""" + signals_id: str | None = None + """An optional Radar signals ID to correlate client-side signals with this authentication attempt.""" + + @classmethod + def from_dict( + cls, data: dict[str, Any] + ) -> EmailCompletionSessionAuthenticateRequest: + """Deserialize from a dictionary.""" + try: + return cls( + client_id=data["client_id"], + client_secret=data["client_secret"], + grant_type=data.get( + "grant_type", "urn:workos:oauth:grant-type:email-completion" + ), + email_completion_token=data["email_completion_token"], + email=data["email"], + ip_address=data.get("ip_address"), + device_id=data.get("device_id"), + user_agent=data.get("user_agent"), + signals_id=data.get("signals_id"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("EmailCompletionSessionAuthenticateRequest", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["client_id"] = self.client_id + result["client_secret"] = self.client_secret + result["grant_type"] = self.grant_type + result["email_completion_token"] = self.email_completion_token + result["email"] = self.email + if self.ip_address is not None: + result["ip_address"] = self.ip_address + if self.device_id is not None: + result["device_id"] = self.device_id + if self.user_agent is not None: + result["user_agent"] = self.user_agent + if self.signals_id is not None: + result["signals_id"] = self.signals_id + return result diff --git a/tests/fixtures/email_completion_session_authenticate_request.json b/tests/fixtures/email_completion_session_authenticate_request.json new file mode 100644 index 00000000..7ae6c962 --- /dev/null +++ b/tests/fixtures/email_completion_session_authenticate_request.json @@ -0,0 +1,11 @@ +{ + "client_id": "client_01HXYZ123456789ABCDEFGHIJ", + "client_secret": "sk_test_....", + "grant_type": "urn:workos:oauth:grant-type:email-completion", + "email_completion_token": "JZ8kQ2mR5vT7wX1yA3bC6dE9f", + "email": "marcelina@example.com", + "ip_address": "203.0.113.42", + "device_id": "device_01HXYZ123456789ABCDEFGHIJ", + "user_agent": "Mozilla/5.0", + "signals_id": "01JBS0GN92GC2RJQS4X9DBPQ2A" +} From 198374048dca614ed20bf05c2b7201745a7d2431 Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:09:55 +0000 Subject: [PATCH 6/8] fix(user_management): Update user management API surface --- .../models/authenticate_response_authentication_method.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/workos/common/models/authenticate_response_authentication_method.py b/src/workos/common/models/authenticate_response_authentication_method.py index 53d66a1f..3eb1e9ad 100644 --- a/src/workos/common/models/authenticate_response_authentication_method.py +++ b/src/workos/common/models/authenticate_response_authentication_method.py @@ -17,13 +17,10 @@ class AuthenticateResponseAuthenticationMethod(str, Enum): APPLE_OAUTH = "AppleOAuth" BITBUCKET_OAUTH = "BitbucketOAuth" CROSS_APP_AUTH = "CrossAppAuth" - DISCORD_OAUTH = "DiscordOAuth" EXTERNAL_AUTH = "ExternalAuth" GIT_HUB_OAUTH = "GitHubOAuth" GIT_LAB_OAUTH = "GitLabOAuth" GOOGLE_OAUTH = "GoogleOAuth" - GROK_OAUTH = "GrokOAuth" - XO_AUTH = "XOAuth" INTUIT_OAUTH = "IntuitOAuth" LINKED_IN_OAUTH = "LinkedInOAuth" MICROSOFT_OAUTH = "MicrosoftOAuth" @@ -55,13 +52,10 @@ def _missing_( "AppleOAuth", "BitbucketOAuth", "CrossAppAuth", - "DiscordOAuth", "ExternalAuth", "GitHubOAuth", "GitLabOAuth", "GoogleOAuth", - "GrokOAuth", - "XOAuth", "IntuitOAuth", "LinkedInOAuth", "MicrosoftOAuth", From cad3eb963742bbb16eb45fb4778081d29a1800b6 Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:09:56 +0000 Subject: [PATCH 7/8] chore(generated): regenerate shared files for AdminPortal, Agents, ApiKeys, AuditLogs, Authorization, ClientApi, Connect, DirectorySync, Events, FeatureFlags, Groups, MultiFactorAuth, OrganizationDomains, OrganizationMembership, Organizations, Pipes, PipesProvider, PlatformTeams, Radar, SSO, UserManagement, Vault, Webhooks, Widgets --- .last-synced-sha | 2 +- .oagen-manifest.json | 313 ++++ src/workos/_client.py | 61 +- src/workos/agents/_resource.py | 1125 +++++++++++++- src/workos/agents/models/__init__.py | 33 + src/workos/agents/models/agent_blueprint.py | 75 + .../models/agent_blueprint_invocable_by.py | 9 + .../models/agent_blueprint_session_setting.py | 9 + .../models/agent_blueprints_create_request.py | 64 + ..._blueprints_create_request_invocable_by.py | 38 + ...ueprints_create_request_session_setting.py | 42 + ...ent_blueprints_token_mint_token_request.py | 61 + .../models/agent_blueprints_update_request.py | 70 + ..._blueprints_update_request_invocable_by.py | 11 + ...ueprints_update_request_session_setting.py | 45 + src/workos/agents/models/agent_instance.py | 66 + .../agents/models/agent_instance_session.py | 72 + src/workos/agents/models/agent_token.py | 60 + src/workos/authorization/_resource.py | 44 +- src/workos/authorization/models/__init__.py | 1 - src/workos/common/__init__.py | 58 + src/workos/common/models/__init__.py | 90 ++ .../common/models/agent_blueprint_created.py | 59 + .../models/agent_blueprint_created_data.py | 78 + ...ent_blueprint_created_data_invocable_by.py | 36 + ..._blueprint_created_data_session_setting.py | 40 + .../common/models/agent_blueprint_deleted.py | 59 + .../models/agent_blueprint_deleted_data.py | 48 + .../common/models/agent_blueprint_updated.py | 59 + .../models/agent_blueprint_updated_data.py | 7 + ...ent_blueprint_updated_data_invocable_by.py | 9 + ..._blueprint_updated_data_session_setting.py | 11 + ...lueprints_token_mint_token_request_type.py | 33 + .../common/models/agent_instance_created.py | 59 + .../models/agent_instance_created_data.py | 66 + .../agent_instance_created_data_type.py | 27 + .../common/models/agent_instance_deleted.py | 59 + .../models/agent_instance_deleted_data.py | 7 + .../agent_instance_deleted_data_type.py | 8 + .../models/agent_instance_session_created.py | 59 + .../agent_instance_session_created_data.py | 67 + .../models/agent_instance_session_revoked.py | 59 + .../agent_instance_session_revoked_data.py | 63 + .../models/agent_instance_session_status.py | 28 + .../common/models/agent_instance_type.py | 8 + .../authentication_oauth_failed_data.py | 5 + .../authentication_oauth_succeeded_data.py | 5 + ...nnection_activated_data_connection_type.py | 6 - src/workos/common/models/connection_type.py | 6 - ...dc_options_id_token_signature_algorithm.py | 54 + ...idc_options_token_authentication_method.py | 32 + .../models/create_webhook_endpoint_events.py | 14 + .../models/invite_it_contact_intents.py | 32 + ...dc_options_id_token_signature_algorithm.py | 12 + ...idc_options_token_authentication_method.py | 12 + .../models/resource_export_completed.py | 59 + .../models/resource_export_completed_data.py | 47 + ...rce_export_completed_data_resource_type.py | 32 + .../common/models/resource_export_created.py | 59 + .../models/resource_export_created_data.py | 7 + ...ource_export_created_data_resource_type.py | 12 + .../models/resource_export_downloaded.py | 59 + .../models/resource_export_downloaded_data.py | 7 + ...ce_export_downloaded_data_resource_type.py | 12 + .../common/models/resource_export_failed.py | 59 + .../models/resource_export_failed_data.py | 7 + ...source_export_failed_data_resource_type.py | 12 + .../models/session_created_data_status.py | 28 +- .../models/session_revoked_data_status.py | 5 +- .../common/models/team_production_state.py | 31 + .../common/models/token_query_grant_type.py | 17 + .../user_identities_get_item_provider.py | 6 - .../common/models/user_sessions_status.py | 5 +- .../common/models/waitlist_entry_state.py | 21 + src/workos/common/models/waitlist_user.py | 23 +- .../common/models/waitlist_user_state.py | 35 +- src/workos/events/models/event_schema.py | 37 + src/workos/organizations/_resource.py | 371 ++++- src/workos/organizations/models/__init__.py | 7 + .../organizations/models/create_it_contact.py | 32 + .../organizations/models/invite_it_contact.py | 39 + src/workos/organizations/models/it_contact.py | 49 + .../organizations/models/it_contact_list.py | 48 + .../models/it_contact_list_list_metadata.py | 42 + .../models/organization_input.py | 4 +- .../models/custom_provider_definition.py | 6 +- src/workos/pipes/models/data_integration.py | 4 +- .../update_custom_provider_definition.py | 6 +- src/workos/platform_teams/__init__.py | 5 + src/workos/platform_teams/_resource.py | 161 ++ src/workos/platform_teams/models/__init__.py | 4 + .../platform_teams/models/create_team.py | 36 + src/workos/platform_teams/models/team.py | 71 + .../sso/models/connections_connection_type.py | 6 - src/workos/sso/models/create_connection.py | 78 + .../create_connection_attribute_maps.py | 45 + .../sso/models/create_connection_key_pair.py | 36 + .../models/create_connection_oidc_options.py | 115 ++ .../models/create_connection_saml_options.py | 83 ++ .../create_connection_standard_attributes.py | 62 + src/workos/sso/models/patch_connection.py | 76 + .../models/patch_connection_attribute_maps.py | 45 + .../models/patch_connection_oidc_options.py | 107 ++ .../models/patch_connection_saml_options.py | 59 + .../patch_connection_standard_attributes.py | 7 + src/workos/types/platform_teams/__init__.py | 3 + tests/fixtures/agent_blueprint.json | 25 + tests/fixtures/agent_blueprint_created.json | 51 + .../agent_blueprint_created_data.json | 25 + ...t_blueprint_created_data_invocable_by.json | 8 + ...lueprint_created_data_session_setting.json | 5 + tests/fixtures/agent_blueprint_deleted.json | 33 + .../agent_blueprint_deleted_data.json | 7 + .../agent_blueprint_invocable_by.json | 8 + .../agent_blueprint_session_setting.json | 5 + tests/fixtures/agent_blueprint_updated.json | 51 + .../agent_blueprint_updated_data.json | 25 + ...t_blueprint_updated_data_invocable_by.json | 8 + ...lueprint_updated_data_session_setting.json | 5 + .../agent_blueprints_create_request.json | 21 + ...lueprints_create_request_invocable_by.json | 8 + ...prints_create_request_session_setting.json | 5 + ...t_blueprints_token_mint_token_request.json | 8 + .../agent_blueprints_update_request.json | 21 + ...lueprints_update_request_invocable_by.json | 8 + ...prints_update_request_session_setting.json | 5 + tests/fixtures/agent_instance.json | 10 + tests/fixtures/agent_instance_created.json | 36 + .../fixtures/agent_instance_created_data.json | 10 + tests/fixtures/agent_instance_deleted.json | 36 + .../fixtures/agent_instance_deleted_data.json | 10 + tests/fixtures/agent_instance_session.json | 10 + .../agent_instance_session_created.json | 39 + .../agent_instance_session_created_data.json | 13 + .../agent_instance_session_revoked.json | 36 + .../agent_instance_session_revoked_data.json | 10 + tests/fixtures/agent_token.json | 12 + .../fixtures/authentication_oauth_failed.json | 3 +- .../authentication_oauth_failed_data.json | 3 +- .../authentication_oauth_succeeded.json | 3 +- .../authentication_oauth_succeeded_data.json | 3 +- tests/fixtures/create_connection.json | 53 + .../create_connection_attribute_maps.json | 13 + .../fixtures/create_connection_key_pair.json | 4 + .../create_connection_oidc_options.json | 14 + .../create_connection_saml_options.json | 20 + ...create_connection_standard_attributes.json | 8 + tests/fixtures/create_it_contact.json | 3 + .../create_saml_idp_signing_certificate.json | 3 + tests/fixtures/create_team.json | 4 + tests/fixtures/create_waitlist_entry.json | 7 + ...ons_upsert_client_credentials_request.json | 2 +- tests/fixtures/invite_it_contact.json | 6 + tests/fixtures/it_contact.json | 7 + tests/fixtures/it_contact_list.json | 16 + .../it_contact_list_list_metadata.json | 4 + tests/fixtures/list_agent_blueprint.json | 33 + tests/fixtures/list_agent_instance.json | 18 + .../fixtures/list_agent_instance_session.json | 18 + tests/fixtures/list_waitlist.json | 14 + tests/fixtures/list_waitlist_entry.json | 21 + tests/fixtures/patch_connection.json | 35 + .../patch_connection_attribute_maps.json | 13 + .../patch_connection_oidc_options.json | 10 + .../patch_connection_saml_options.json | 7 + .../patch_connection_standard_attributes.json | 8 + tests/fixtures/resource_export_completed.json | 30 + .../resource_export_completed_data.json | 4 + tests/fixtures/resource_export_created.json | 30 + .../resource_export_created_data.json | 4 + .../fixtures/resource_export_downloaded.json | 30 + .../resource_export_downloaded_data.json | 4 + tests/fixtures/resource_export_failed.json | 30 + .../fixtures/resource_export_failed_data.json | 4 + .../saml_idp_signing_certificate.json | 8 + .../saml_idp_signing_certificate_list.json | 13 + .../saml_sp_encryption_certificate.json | 8 + .../saml_sp_encryption_certificate_list.json | 13 + .../fixtures/saml_sp_signing_certificate.json | 8 + tests/fixtures/team.json | 9 + tests/fixtures/token_query.json | 5 +- tests/fixtures/waitlist.json | 6 + tests/fixtures/waitlist_entry.json | 13 + tests/fixtures/waitlist_user.json | 7 +- tests/fixtures/waitlist_user_approved.json | 7 +- tests/fixtures/waitlist_user_created.json | 7 +- tests/fixtures/waitlist_user_denied.json | 7 +- tests/test_agents.py | 619 ++++++-- tests/test_agents_models_round_trip.py | 358 +++++ tests/test_authorization.py | 60 +- tests/test_common_models_round_trip.py | 1310 ++++++++++++++++- tests/test_organization_membership.py | 8 +- tests/test_organizations.py | 133 ++ tests/test_organizations_models_round_trip.py | 80 + tests/test_platform_teams.py | 256 ++++ .../test_platform_teams_models_round_trip.py | 63 + 196 files changed, 9275 insertions(+), 429 deletions(-) create mode 100644 src/workos/agents/models/agent_blueprint.py create mode 100644 src/workos/agents/models/agent_blueprint_invocable_by.py create mode 100644 src/workos/agents/models/agent_blueprint_session_setting.py create mode 100644 src/workos/agents/models/agent_blueprints_create_request.py create mode 100644 src/workos/agents/models/agent_blueprints_create_request_invocable_by.py create mode 100644 src/workos/agents/models/agent_blueprints_create_request_session_setting.py create mode 100644 src/workos/agents/models/agent_blueprints_token_mint_token_request.py create mode 100644 src/workos/agents/models/agent_blueprints_update_request.py create mode 100644 src/workos/agents/models/agent_blueprints_update_request_invocable_by.py create mode 100644 src/workos/agents/models/agent_blueprints_update_request_session_setting.py create mode 100644 src/workos/agents/models/agent_instance.py create mode 100644 src/workos/agents/models/agent_instance_session.py create mode 100644 src/workos/agents/models/agent_token.py create mode 100644 src/workos/common/models/agent_blueprint_created.py create mode 100644 src/workos/common/models/agent_blueprint_created_data.py create mode 100644 src/workos/common/models/agent_blueprint_created_data_invocable_by.py create mode 100644 src/workos/common/models/agent_blueprint_created_data_session_setting.py create mode 100644 src/workos/common/models/agent_blueprint_deleted.py create mode 100644 src/workos/common/models/agent_blueprint_deleted_data.py create mode 100644 src/workos/common/models/agent_blueprint_updated.py create mode 100644 src/workos/common/models/agent_blueprint_updated_data.py create mode 100644 src/workos/common/models/agent_blueprint_updated_data_invocable_by.py create mode 100644 src/workos/common/models/agent_blueprint_updated_data_session_setting.py create mode 100644 src/workos/common/models/agent_blueprints_token_mint_token_request_type.py create mode 100644 src/workos/common/models/agent_instance_created.py create mode 100644 src/workos/common/models/agent_instance_created_data.py create mode 100644 src/workos/common/models/agent_instance_created_data_type.py create mode 100644 src/workos/common/models/agent_instance_deleted.py create mode 100644 src/workos/common/models/agent_instance_deleted_data.py create mode 100644 src/workos/common/models/agent_instance_deleted_data_type.py create mode 100644 src/workos/common/models/agent_instance_session_created.py create mode 100644 src/workos/common/models/agent_instance_session_created_data.py create mode 100644 src/workos/common/models/agent_instance_session_revoked.py create mode 100644 src/workos/common/models/agent_instance_session_revoked_data.py create mode 100644 src/workos/common/models/agent_instance_session_status.py create mode 100644 src/workos/common/models/agent_instance_type.py create mode 100644 src/workos/common/models/create_connection_oidc_options_id_token_signature_algorithm.py create mode 100644 src/workos/common/models/create_connection_oidc_options_token_authentication_method.py create mode 100644 src/workos/common/models/invite_it_contact_intents.py create mode 100644 src/workos/common/models/patch_connection_oidc_options_id_token_signature_algorithm.py create mode 100644 src/workos/common/models/patch_connection_oidc_options_token_authentication_method.py create mode 100644 src/workos/common/models/resource_export_completed.py create mode 100644 src/workos/common/models/resource_export_completed_data.py create mode 100644 src/workos/common/models/resource_export_completed_data_resource_type.py create mode 100644 src/workos/common/models/resource_export_created.py create mode 100644 src/workos/common/models/resource_export_created_data.py create mode 100644 src/workos/common/models/resource_export_created_data_resource_type.py create mode 100644 src/workos/common/models/resource_export_downloaded.py create mode 100644 src/workos/common/models/resource_export_downloaded_data.py create mode 100644 src/workos/common/models/resource_export_downloaded_data_resource_type.py create mode 100644 src/workos/common/models/resource_export_failed.py create mode 100644 src/workos/common/models/resource_export_failed_data.py create mode 100644 src/workos/common/models/resource_export_failed_data_resource_type.py create mode 100644 src/workos/common/models/team_production_state.py create mode 100644 src/workos/common/models/token_query_grant_type.py create mode 100644 src/workos/common/models/waitlist_entry_state.py create mode 100644 src/workos/organizations/models/create_it_contact.py create mode 100644 src/workos/organizations/models/invite_it_contact.py create mode 100644 src/workos/organizations/models/it_contact.py create mode 100644 src/workos/organizations/models/it_contact_list.py create mode 100644 src/workos/organizations/models/it_contact_list_list_metadata.py create mode 100644 src/workos/platform_teams/__init__.py create mode 100644 src/workos/platform_teams/_resource.py create mode 100644 src/workos/platform_teams/models/__init__.py create mode 100644 src/workos/platform_teams/models/create_team.py create mode 100644 src/workos/platform_teams/models/team.py create mode 100644 src/workos/sso/models/create_connection.py create mode 100644 src/workos/sso/models/create_connection_attribute_maps.py create mode 100644 src/workos/sso/models/create_connection_key_pair.py create mode 100644 src/workos/sso/models/create_connection_oidc_options.py create mode 100644 src/workos/sso/models/create_connection_saml_options.py create mode 100644 src/workos/sso/models/create_connection_standard_attributes.py create mode 100644 src/workos/sso/models/patch_connection.py create mode 100644 src/workos/sso/models/patch_connection_attribute_maps.py create mode 100644 src/workos/sso/models/patch_connection_oidc_options.py create mode 100644 src/workos/sso/models/patch_connection_saml_options.py create mode 100644 src/workos/sso/models/patch_connection_standard_attributes.py create mode 100644 src/workos/types/platform_teams/__init__.py create mode 100644 tests/fixtures/agent_blueprint.json create mode 100644 tests/fixtures/agent_blueprint_created.json create mode 100644 tests/fixtures/agent_blueprint_created_data.json create mode 100644 tests/fixtures/agent_blueprint_created_data_invocable_by.json create mode 100644 tests/fixtures/agent_blueprint_created_data_session_setting.json create mode 100644 tests/fixtures/agent_blueprint_deleted.json create mode 100644 tests/fixtures/agent_blueprint_deleted_data.json create mode 100644 tests/fixtures/agent_blueprint_invocable_by.json create mode 100644 tests/fixtures/agent_blueprint_session_setting.json create mode 100644 tests/fixtures/agent_blueprint_updated.json create mode 100644 tests/fixtures/agent_blueprint_updated_data.json create mode 100644 tests/fixtures/agent_blueprint_updated_data_invocable_by.json create mode 100644 tests/fixtures/agent_blueprint_updated_data_session_setting.json create mode 100644 tests/fixtures/agent_blueprints_create_request.json create mode 100644 tests/fixtures/agent_blueprints_create_request_invocable_by.json create mode 100644 tests/fixtures/agent_blueprints_create_request_session_setting.json create mode 100644 tests/fixtures/agent_blueprints_token_mint_token_request.json create mode 100644 tests/fixtures/agent_blueprints_update_request.json create mode 100644 tests/fixtures/agent_blueprints_update_request_invocable_by.json create mode 100644 tests/fixtures/agent_blueprints_update_request_session_setting.json create mode 100644 tests/fixtures/agent_instance.json create mode 100644 tests/fixtures/agent_instance_created.json create mode 100644 tests/fixtures/agent_instance_created_data.json create mode 100644 tests/fixtures/agent_instance_deleted.json create mode 100644 tests/fixtures/agent_instance_deleted_data.json create mode 100644 tests/fixtures/agent_instance_session.json create mode 100644 tests/fixtures/agent_instance_session_created.json create mode 100644 tests/fixtures/agent_instance_session_created_data.json create mode 100644 tests/fixtures/agent_instance_session_revoked.json create mode 100644 tests/fixtures/agent_instance_session_revoked_data.json create mode 100644 tests/fixtures/agent_token.json create mode 100644 tests/fixtures/create_connection.json create mode 100644 tests/fixtures/create_connection_attribute_maps.json create mode 100644 tests/fixtures/create_connection_key_pair.json create mode 100644 tests/fixtures/create_connection_oidc_options.json create mode 100644 tests/fixtures/create_connection_saml_options.json create mode 100644 tests/fixtures/create_connection_standard_attributes.json create mode 100644 tests/fixtures/create_it_contact.json create mode 100644 tests/fixtures/create_saml_idp_signing_certificate.json create mode 100644 tests/fixtures/create_team.json create mode 100644 tests/fixtures/create_waitlist_entry.json create mode 100644 tests/fixtures/invite_it_contact.json create mode 100644 tests/fixtures/it_contact.json create mode 100644 tests/fixtures/it_contact_list.json create mode 100644 tests/fixtures/it_contact_list_list_metadata.json create mode 100644 tests/fixtures/list_agent_blueprint.json create mode 100644 tests/fixtures/list_agent_instance.json create mode 100644 tests/fixtures/list_agent_instance_session.json create mode 100644 tests/fixtures/list_waitlist.json create mode 100644 tests/fixtures/list_waitlist_entry.json create mode 100644 tests/fixtures/patch_connection.json create mode 100644 tests/fixtures/patch_connection_attribute_maps.json create mode 100644 tests/fixtures/patch_connection_oidc_options.json create mode 100644 tests/fixtures/patch_connection_saml_options.json create mode 100644 tests/fixtures/patch_connection_standard_attributes.json create mode 100644 tests/fixtures/resource_export_completed.json create mode 100644 tests/fixtures/resource_export_completed_data.json create mode 100644 tests/fixtures/resource_export_created.json create mode 100644 tests/fixtures/resource_export_created_data.json create mode 100644 tests/fixtures/resource_export_downloaded.json create mode 100644 tests/fixtures/resource_export_downloaded_data.json create mode 100644 tests/fixtures/resource_export_failed.json create mode 100644 tests/fixtures/resource_export_failed_data.json create mode 100644 tests/fixtures/saml_idp_signing_certificate.json create mode 100644 tests/fixtures/saml_idp_signing_certificate_list.json create mode 100644 tests/fixtures/saml_sp_encryption_certificate.json create mode 100644 tests/fixtures/saml_sp_encryption_certificate_list.json create mode 100644 tests/fixtures/saml_sp_signing_certificate.json create mode 100644 tests/fixtures/team.json create mode 100644 tests/fixtures/waitlist.json create mode 100644 tests/fixtures/waitlist_entry.json create mode 100644 tests/test_platform_teams.py create mode 100644 tests/test_platform_teams_models_round_trip.py diff --git a/.last-synced-sha b/.last-synced-sha index 066427ca..0f267924 100644 --- a/.last-synced-sha +++ b/.last-synced-sha @@ -1 +1 @@ -a07d8e7988d035c2d727787f18f64d71b4b89a84 +d61348070f219d16b6285f205986c2d332bf6e9c diff --git a/.oagen-manifest.json b/.oagen-manifest.json index 08501acd..fb34b225 100644 --- a/.oagen-manifest.json +++ b/.oagen-manifest.json @@ -14,11 +14,24 @@ "src/workos/agents/models/agent_admin_link_claim_attempt_to_external_user_request.py", "src/workos/agents/models/agent_admin_link_claim_attempt_to_external_user_request_user.py", "src/workos/agents/models/agent_admin_validate_credential_request.py", + "src/workos/agents/models/agent_blueprint.py", + "src/workos/agents/models/agent_blueprint_invocable_by.py", + "src/workos/agents/models/agent_blueprint_session_setting.py", + "src/workos/agents/models/agent_blueprints_create_request.py", + "src/workos/agents/models/agent_blueprints_create_request_invocable_by.py", + "src/workos/agents/models/agent_blueprints_create_request_session_setting.py", + "src/workos/agents/models/agent_blueprints_token_mint_token_request.py", + "src/workos/agents/models/agent_blueprints_update_request.py", + "src/workos/agents/models/agent_blueprints_update_request_invocable_by.py", + "src/workos/agents/models/agent_blueprints_update_request_session_setting.py", "src/workos/agents/models/agent_credential_validation.py", + "src/workos/agents/models/agent_instance.py", + "src/workos/agents/models/agent_instance_session.py", "src/workos/agents/models/agent_registration.py", "src/workos/agents/models/agent_registration_agent_identity.py", "src/workos/agents/models/agent_registration_claim.py", "src/workos/agents/models/agent_registration_claim_claim_completion.py", + "src/workos/agents/models/agent_token.py", "src/workos/agents/models/claim_view_response.py", "src/workos/agents/models/claim_view_response_organization.py", "src/workos/api_keys/__init__.py", @@ -98,6 +111,29 @@ "src/workos/common/models/actor.py", "src/workos/common/models/add_role_permission.py", "src/workos/common/models/agent_admin_validate_credential_request_type.py", + "src/workos/common/models/agent_blueprint_created.py", + "src/workos/common/models/agent_blueprint_created_data.py", + "src/workos/common/models/agent_blueprint_created_data_invocable_by.py", + "src/workos/common/models/agent_blueprint_created_data_session_setting.py", + "src/workos/common/models/agent_blueprint_deleted.py", + "src/workos/common/models/agent_blueprint_deleted_data.py", + "src/workos/common/models/agent_blueprint_updated.py", + "src/workos/common/models/agent_blueprint_updated_data.py", + "src/workos/common/models/agent_blueprint_updated_data_invocable_by.py", + "src/workos/common/models/agent_blueprint_updated_data_session_setting.py", + "src/workos/common/models/agent_blueprints_token_mint_token_request_type.py", + "src/workos/common/models/agent_instance_created.py", + "src/workos/common/models/agent_instance_created_data.py", + "src/workos/common/models/agent_instance_created_data_type.py", + "src/workos/common/models/agent_instance_deleted.py", + "src/workos/common/models/agent_instance_deleted_data.py", + "src/workos/common/models/agent_instance_deleted_data_type.py", + "src/workos/common/models/agent_instance_session_created.py", + "src/workos/common/models/agent_instance_session_created_data.py", + "src/workos/common/models/agent_instance_session_revoked.py", + "src/workos/common/models/agent_instance_session_revoked_data.py", + "src/workos/common/models/agent_instance_session_status.py", + "src/workos/common/models/agent_instance_type.py", "src/workos/common/models/agent_registration_claim_attempt_created.py", "src/workos/common/models/agent_registration_claim_attempt_created_data.py", "src/workos/common/models/agent_registration_claim_completed.py", @@ -239,6 +275,8 @@ "src/workos/common/models/connection_state.py", "src/workos/common/models/connection_status.py", "src/workos/common/models/connection_type.py", + "src/workos/common/models/create_connection_oidc_options_id_token_signature_algorithm.py", + "src/workos/common/models/create_connection_oidc_options_token_authentication_method.py", "src/workos/common/models/create_data_integration_auth_methods.py", "src/workos/common/models/create_user_invite_options_locale.py", "src/workos/common/models/create_user_password_hash_type.py", @@ -361,6 +399,7 @@ "src/workos/common/models/invitation_revoked_data.py", "src/workos/common/models/invitation_revoked_data_state.py", "src/workos/common/models/invitation_state.py", + "src/workos/common/models/invite_it_contact_intents.py", "src/workos/common/models/list_metadata.py", "src/workos/common/models/magic_auth_created.py", "src/workos/common/models/magic_auth_created_data.py", @@ -426,6 +465,8 @@ "src/workos/common/models/password_reset_created_data.py", "src/workos/common/models/password_reset_succeeded.py", "src/workos/common/models/password_reset_succeeded_data.py", + "src/workos/common/models/patch_connection_oidc_options_id_token_signature_algorithm.py", + "src/workos/common/models/patch_connection_oidc_options_token_authentication_method.py", "src/workos/common/models/permission_created.py", "src/workos/common/models/permission_created_data.py", "src/workos/common/models/permission_deleted.py", @@ -448,6 +489,18 @@ "src/workos/common/models/radar_standalone_response_control.py", "src/workos/common/models/radar_standalone_response_verdict.py", "src/workos/common/models/resend_user_invite_options_locale.py", + "src/workos/common/models/resource_export_completed.py", + "src/workos/common/models/resource_export_completed_data.py", + "src/workos/common/models/resource_export_completed_data_resource_type.py", + "src/workos/common/models/resource_export_created.py", + "src/workos/common/models/resource_export_created_data.py", + "src/workos/common/models/resource_export_created_data_resource_type.py", + "src/workos/common/models/resource_export_downloaded.py", + "src/workos/common/models/resource_export_downloaded_data.py", + "src/workos/common/models/resource_export_downloaded_data_resource_type.py", + "src/workos/common/models/resource_export_failed.py", + "src/workos/common/models/resource_export_failed_data.py", + "src/workos/common/models/resource_export_failed_data_resource_type.py", "src/workos/common/models/role_created.py", "src/workos/common/models/role_created_data.py", "src/workos/common/models/role_deleted.py", @@ -466,6 +519,9 @@ "src/workos/common/models/session_revoked_data_impersonator.py", "src/workos/common/models/session_revoked_data_status.py", "src/workos/common/models/slim_role.py", + "src/workos/common/models/team_production_state.py", + "src/workos/common/models/token_query_grant_type.py", + "src/workos/common/models/update_audit_logs_retention_retention_period.py", "src/workos/common/models/update_custom_provider_definition_authenticate_via.py", "src/workos/common/models/update_user_password_hash_type.py", "src/workos/common/models/update_user_password_salt_position.py", @@ -523,6 +579,7 @@ "src/workos/common/models/vault_names_listed.py", "src/workos/common/models/vault_names_listed_data.py", "src/workos/common/models/vault_names_listed_data_actor_source.py", + "src/workos/common/models/waitlist_entry_state.py", "src/workos/common/models/waitlist_user.py", "src/workos/common/models/waitlist_user_approved.py", "src/workos/common/models/waitlist_user_created.py", @@ -595,6 +652,11 @@ "src/workos/organizations/models/audit_log_configuration.py", "src/workos/organizations/models/audit_log_configuration_log_stream.py", "src/workos/organizations/models/audit_logs_retention.py", + "src/workos/organizations/models/create_it_contact.py", + "src/workos/organizations/models/invite_it_contact.py", + "src/workos/organizations/models/it_contact.py", + "src/workos/organizations/models/it_contact_list.py", + "src/workos/organizations/models/it_contact_list_list_metadata.py", "src/workos/organizations/models/organization.py", "src/workos/organizations/models/organization_authorized_connect_application_list_data.py", "src/workos/organizations/models/organization_domain_data.py", @@ -635,6 +697,11 @@ "src/workos/pipes_provider/models/data_integration_configuration_list_response.py", "src/workos/pipes_provider/models/data_integration_configuration_response.py", "src/workos/pipes_provider/models/data_integration_credentials.py", + "src/workos/platform_teams/__init__.py", + "src/workos/platform_teams/_resource.py", + "src/workos/platform_teams/models/__init__.py", + "src/workos/platform_teams/models/create_team.py", + "src/workos/platform_teams/models/team.py", "src/workos/radar/__init__.py", "src/workos/radar/_resource.py", "src/workos/radar/models/__init__.py", @@ -652,8 +719,26 @@ "src/workos/sso/models/connection.py", "src/workos/sso/models/connection_domain.py", "src/workos/sso/models/connections_connection_type.py", + "src/workos/sso/models/create_connection.py", + "src/workos/sso/models/create_connection_attribute_maps.py", + "src/workos/sso/models/create_connection_key_pair.py", + "src/workos/sso/models/create_connection_oidc_options.py", + "src/workos/sso/models/create_connection_saml_options.py", + "src/workos/sso/models/create_connection_standard_attributes.py", + "src/workos/sso/models/create_saml_idp_signing_certificate.py", + "src/workos/sso/models/patch_connection.py", + "src/workos/sso/models/patch_connection_attribute_maps.py", + "src/workos/sso/models/patch_connection_oidc_options.py", + "src/workos/sso/models/patch_connection_saml_options.py", + "src/workos/sso/models/patch_connection_standard_attributes.py", "src/workos/sso/models/profile.py", + "src/workos/sso/models/saml_idp_signing_certificate.py", + "src/workos/sso/models/saml_idp_signing_certificate_list.py", + "src/workos/sso/models/saml_sp_encryption_certificate.py", + "src/workos/sso/models/saml_sp_encryption_certificate_list.py", + "src/workos/sso/models/saml_sp_signing_certificate.py", "src/workos/sso/models/sso_authorize_url_response.py", + "src/workos/sso/models/sso_grant_type.py", "src/workos/sso/models/sso_logout_authorize_request.py", "src/workos/sso/models/sso_logout_authorize_response.py", "src/workos/sso/models/sso_provider.py", @@ -678,6 +763,7 @@ "src/workos/types/organizations/__init__.py", "src/workos/types/pipes/__init__.py", "src/workos/types/pipes_provider/__init__.py", + "src/workos/types/platform_teams/__init__.py", "src/workos/types/radar/__init__.py", "src/workos/types/sso/__init__.py", "src/workos/types/user_management/__init__.py", @@ -701,11 +787,13 @@ "src/workos/user_management/models/create_user.py", "src/workos/user_management/models/create_user_api_key.py", "src/workos/user_management/models/create_user_invite_options.py", + "src/workos/user_management/models/create_waitlist_entry.py", "src/workos/user_management/models/device_authorization_response.py", "src/workos/user_management/models/device_code_session_authenticate_request.py", "src/workos/user_management/models/email_change.py", "src/workos/user_management/models/email_change_confirmation.py", "src/workos/user_management/models/email_change_confirmation_user.py", + "src/workos/user_management/models/email_completion_session_authenticate_request.py", "src/workos/user_management/models/email_verification.py", "src/workos/user_management/models/email_verification_code_session_authenticate_request.py", "src/workos/user_management/models/invitation.py", @@ -743,8 +831,11 @@ "src/workos/user_management/models/user_invite.py", "src/workos/user_management/models/user_management_authentication_provider.py", "src/workos/user_management/models/user_management_authentication_screen_hint.py", + "src/workos/user_management/models/user_management_waitlists_state.py", "src/workos/user_management/models/verify_email_address.py", "src/workos/user_management/models/verify_email_response.py", + "src/workos/user_management/models/waitlist.py", + "src/workos/user_management/models/waitlist_entry.py", "src/workos/vault/__init__.py", "src/workos/vault/_resource.py", "src/workos/vault/models/__init__.py", @@ -784,7 +875,37 @@ "tests/fixtures/agent_admin_link_claim_attempt_to_external_user_request.json", "tests/fixtures/agent_admin_link_claim_attempt_to_external_user_request_user.json", "tests/fixtures/agent_admin_validate_credential_request.json", + "tests/fixtures/agent_blueprint.json", + "tests/fixtures/agent_blueprint_created.json", + "tests/fixtures/agent_blueprint_created_data.json", + "tests/fixtures/agent_blueprint_created_data_invocable_by.json", + "tests/fixtures/agent_blueprint_created_data_session_setting.json", + "tests/fixtures/agent_blueprint_deleted.json", + "tests/fixtures/agent_blueprint_deleted_data.json", + "tests/fixtures/agent_blueprint_invocable_by.json", + "tests/fixtures/agent_blueprint_session_setting.json", + "tests/fixtures/agent_blueprint_updated.json", + "tests/fixtures/agent_blueprint_updated_data.json", + "tests/fixtures/agent_blueprint_updated_data_invocable_by.json", + "tests/fixtures/agent_blueprint_updated_data_session_setting.json", + "tests/fixtures/agent_blueprints_create_request.json", + "tests/fixtures/agent_blueprints_create_request_invocable_by.json", + "tests/fixtures/agent_blueprints_create_request_session_setting.json", + "tests/fixtures/agent_blueprints_token_mint_token_request.json", + "tests/fixtures/agent_blueprints_update_request.json", + "tests/fixtures/agent_blueprints_update_request_invocable_by.json", + "tests/fixtures/agent_blueprints_update_request_session_setting.json", "tests/fixtures/agent_credential_validation.json", + "tests/fixtures/agent_instance.json", + "tests/fixtures/agent_instance_created.json", + "tests/fixtures/agent_instance_created_data.json", + "tests/fixtures/agent_instance_deleted.json", + "tests/fixtures/agent_instance_deleted_data.json", + "tests/fixtures/agent_instance_session.json", + "tests/fixtures/agent_instance_session_created.json", + "tests/fixtures/agent_instance_session_created_data.json", + "tests/fixtures/agent_instance_session_revoked.json", + "tests/fixtures/agent_instance_session_revoked_data.json", "tests/fixtures/agent_registration.json", "tests/fixtures/agent_registration_agent_identity.json", "tests/fixtures/agent_registration_claim.json", @@ -810,6 +931,7 @@ "tests/fixtures/agent_registration_refreshed_data.json", "tests/fixtures/agent_registration_revoked.json", "tests/fixtures/agent_registration_revoked_data.json", + "tests/fixtures/agent_token.json", "tests/fixtures/api_key.json", "tests/fixtures/api_key_created.json", "tests/fixtures/api_key_created_data.json", @@ -946,6 +1068,12 @@ "tests/fixtures/cors_origin_response.json", "tests/fixtures/create_authorization_permission.json", "tests/fixtures/create_authorization_resource.json", + "tests/fixtures/create_connection.json", + "tests/fixtures/create_connection_attribute_maps.json", + "tests/fixtures/create_connection_key_pair.json", + "tests/fixtures/create_connection_oidc_options.json", + "tests/fixtures/create_connection_saml_options.json", + "tests/fixtures/create_connection_standard_attributes.json", "tests/fixtures/create_cors_origin.json", "tests/fixtures/create_data_integration.json", "tests/fixtures/create_data_key_request.json", @@ -953,6 +1081,7 @@ "tests/fixtures/create_group.json", "tests/fixtures/create_group_membership.json", "tests/fixtures/create_group_role_assignment.json", + "tests/fixtures/create_it_contact.json", "tests/fixtures/create_m2m_application.json", "tests/fixtures/create_magic_code_and_return.json", "tests/fixtures/create_oauth_application.json", @@ -964,10 +1093,13 @@ "tests/fixtures/create_password_reset_token.json", "tests/fixtures/create_redirect_uri.json", "tests/fixtures/create_role.json", + "tests/fixtures/create_saml_idp_signing_certificate.json", + "tests/fixtures/create_team.json", "tests/fixtures/create_user.json", "tests/fixtures/create_user_api_key.json", "tests/fixtures/create_user_invite_options.json", "tests/fixtures/create_user_organization_membership.json", + "tests/fixtures/create_waitlist_entry.json", "tests/fixtures/create_webhook_endpoint.json", "tests/fixtures/custom_provider_definition.json", "tests/fixtures/data_integration.json", @@ -1030,6 +1162,7 @@ "tests/fixtures/email_change.json", "tests/fixtures/email_change_confirmation.json", "tests/fixtures/email_change_confirmation_user.json", + "tests/fixtures/email_completion_session_authenticate_request.json", "tests/fixtures/email_verification.json", "tests/fixtures/email_verification_code_session_authenticate_request.json", "tests/fixtures/email_verification_created.json", @@ -1099,9 +1232,16 @@ "tests/fixtures/invitation_resent_data.json", "tests/fixtures/invitation_revoked.json", "tests/fixtures/invitation_revoked_data.json", + "tests/fixtures/invite_it_contact.json", + "tests/fixtures/it_contact.json", + "tests/fixtures/it_contact_list.json", + "tests/fixtures/it_contact_list_list_metadata.json", "tests/fixtures/jwks_response.json", "tests/fixtures/jwks_response_keys.json", "tests/fixtures/jwt_template_response.json", + "tests/fixtures/list_agent_blueprint.json", + "tests/fixtures/list_agent_instance.json", + "tests/fixtures/list_agent_instance_session.json", "tests/fixtures/list_audit_log_action.json", "tests/fixtures/list_audit_log_schema.json", "tests/fixtures/list_authentication_factor.json", @@ -1132,6 +1272,8 @@ "tests/fixtures/list_user_organization_membership_base_list_data.json", "tests/fixtures/list_user_role_assignment.json", "tests/fixtures/list_user_sessions_list_item.json", + "tests/fixtures/list_waitlist.json", + "tests/fixtures/list_waitlist_entry.json", "tests/fixtures/list_webhook_endpoint.json", "tests/fixtures/magic_auth.json", "tests/fixtures/magic_auth_code_session_authenticate_request.json", @@ -1193,6 +1335,11 @@ "tests/fixtures/password_reset_succeeded.json", "tests/fixtures/password_reset_succeeded_data.json", "tests/fixtures/password_session_authenticate_request.json", + "tests/fixtures/patch_connection.json", + "tests/fixtures/patch_connection_attribute_maps.json", + "tests/fixtures/patch_connection_oidc_options.json", + "tests/fixtures/patch_connection_saml_options.json", + "tests/fixtures/patch_connection_standard_attributes.json", "tests/fixtures/permission.json", "tests/fixtures/permission_created.json", "tests/fixtures/permission_created_data.json", @@ -1228,6 +1375,14 @@ "tests/fixtures/replace_group_role_assignments.json", "tests/fixtures/resend_user_invite_options.json", "tests/fixtures/reset_password_response.json", + "tests/fixtures/resource_export_completed.json", + "tests/fixtures/resource_export_completed_data.json", + "tests/fixtures/resource_export_created.json", + "tests/fixtures/resource_export_created_data.json", + "tests/fixtures/resource_export_downloaded.json", + "tests/fixtures/resource_export_downloaded_data.json", + "tests/fixtures/resource_export_failed.json", + "tests/fixtures/resource_export_failed_data.json", "tests/fixtures/revoke_session.json", "tests/fixtures/role.json", "tests/fixtures/role_created.json", @@ -1237,6 +1392,11 @@ "tests/fixtures/role_list.json", "tests/fixtures/role_updated.json", "tests/fixtures/role_updated_data.json", + "tests/fixtures/saml_idp_signing_certificate.json", + "tests/fixtures/saml_idp_signing_certificate_list.json", + "tests/fixtures/saml_sp_encryption_certificate.json", + "tests/fixtures/saml_sp_encryption_certificate_list.json", + "tests/fixtures/saml_sp_signing_certificate.json", "tests/fixtures/send_email_change.json", "tests/fixtures/send_radar_sms_challenge.json", "tests/fixtures/send_radar_sms_challenge_response.json", @@ -1255,6 +1415,7 @@ "tests/fixtures/sso_logout_authorize_response.json", "tests/fixtures/sso_token_response.json", "tests/fixtures/sso_token_response_oauth_token.json", + "tests/fixtures/team.json", "tests/fixtures/token_query.json", "tests/fixtures/update_audit_logs_retention.json", "tests/fixtures/update_authorization_permission.json", @@ -1326,6 +1487,8 @@ "tests/fixtures/verify_email_address.json", "tests/fixtures/verify_email_response.json", "tests/fixtures/version_list_response.json", + "tests/fixtures/waitlist.json", + "tests/fixtures/waitlist_entry.json", "tests/fixtures/waitlist_user.json", "tests/fixtures/waitlist_user_approved.json", "tests/fixtures/waitlist_user_created.json", @@ -1368,6 +1531,8 @@ "tests/test_pipes_models_round_trip.py", "tests/test_pipes_provider.py", "tests/test_pipes_provider_models_round_trip.py", + "tests/test_platform_teams.py", + "tests/test_platform_teams_models_round_trip.py", "tests/test_radar.py", "tests/test_radar_models_round_trip.py", "tests/test_sso.py", @@ -2229,6 +2394,154 @@ "PUT /data-integrations/{slug}/client-credentials": { "sdkMethod": "update_data_integration_client_credentials", "service": "pipes" + }, + "GET /agents/blueprints": { + "sdkMethod": "list_blueprints", + "service": "agents" + }, + "POST /agents/blueprints": { + "sdkMethod": "create_blueprint", + "service": "agents" + }, + "GET /agents/blueprints/{agent_blueprint_id}": { + "sdkMethod": "get_blueprint", + "service": "agents" + }, + "PATCH /agents/blueprints/{agent_blueprint_id}": { + "sdkMethod": "update_blueprint", + "service": "agents" + }, + "DELETE /agents/blueprints/{agent_blueprint_id}": { + "sdkMethod": "delete_blueprint", + "service": "agents" + }, + "POST /agents/blueprints/{agent_blueprint_id}/tokens": { + "sdkMethod": "create_blueprint_token", + "service": "agents" + }, + "GET /agents/instances": { + "sdkMethod": "list_instances", + "service": "agents" + }, + "GET /agents/instances/{agent_instance_id}": { + "sdkMethod": "get_instance", + "service": "agents" + }, + "DELETE /agents/instances/{agent_instance_id}": { + "sdkMethod": "delete_instance", + "service": "agents" + }, + "GET /agents/sessions": { + "sdkMethod": "list_sessions", + "service": "agents" + }, + "GET /agents/sessions/{agent_instance_session_id}": { + "sdkMethod": "get_session", + "service": "agents" + }, + "POST /agents/sessions/{agent_instance_session_id}/revoke": { + "sdkMethod": "revoke_session", + "service": "agents" + }, + "POST /connections": { + "sdkMethod": "create_connection", + "service": "sso" + }, + "GET /connections/{connectionId}/saml_idp_signing_certs": { + "sdkMethod": "list_connection_saml_idp_signing_certs", + "service": "sso" + }, + "POST /connections/{connectionId}/saml_idp_signing_certs": { + "sdkMethod": "create_connection_saml_idp_signing_cert", + "service": "sso" + }, + "DELETE /connections/{connectionId}/saml_idp_signing_certs/{certificateId}": { + "sdkMethod": "delete_connection_saml_idp_signing_cert", + "service": "sso" + }, + "GET /connections/{connectionId}/saml_sp_encryption_certs": { + "sdkMethod": "list_connection_saml_sp_encryption_certs", + "service": "sso" + }, + "POST /connections/{connectionId}/saml_sp_encryption_certs": { + "sdkMethod": "create_connection_saml_sp_encryption_cert", + "service": "sso" + }, + "DELETE /connections/{connectionId}/saml_sp_encryption_certs/{certificateId}": { + "sdkMethod": "delete_connection_saml_sp_encryption_cert", + "service": "sso" + }, + "GET /connections/{connectionId}/saml_sp_signing_cert": { + "sdkMethod": "list_connection_saml_sp_signing_cert", + "service": "sso" + }, + "POST /connections/{connectionId}/saml_sp_signing_cert": { + "sdkMethod": "create_connection_saml_sp_signing_cert", + "service": "sso" + }, + "DELETE /connections/{connectionId}/saml_sp_signing_cert/{certificateId}": { + "sdkMethod": "delete_connection_saml_sp_signing_cert", + "service": "sso" + }, + "PATCH /connections/{id}": { + "sdkMethod": "update_connection", + "service": "sso" + }, + "GET /organizations/{organization_id}/it_contacts": { + "sdkMethod": "list_it_contacts", + "service": "organizations" + }, + "POST /organizations/{organization_id}/it_contacts": { + "sdkMethod": "create_it_contact", + "service": "organizations" + }, + "DELETE /organizations/{organization_id}/it_contacts/{contact_id}": { + "sdkMethod": "delete_it_contact", + "service": "organizations" + }, + "POST /organizations/{organization_id}/it_contacts/{contact_id}/invite": { + "sdkMethod": "invite_it_contact", + "service": "organizations" + }, + "POST /organizations/{organization_id}/it_contacts/{contact_id}/revoke": { + "sdkMethod": "revoke_it_contact", + "service": "organizations" + }, + "POST /platform/teams": { + "sdkMethod": "create_team", + "service": "platform_teams" + }, + "GET /platform/teams/{team_id}": { + "sdkMethod": "get_team", + "service": "platform_teams" + }, + "DELETE /user_management/waitlist_entries/{id}": { + "sdkMethod": "delete_waitlist_entry", + "service": "user_management" + }, + "POST /user_management/waitlist_entries/{id}/approve": { + "sdkMethod": "create_waitlist_entry_approve", + "service": "user_management" + }, + "POST /user_management/waitlist_entries/{id}/deny": { + "sdkMethod": "create_waitlist_entry_deny", + "service": "user_management" + }, + "GET /user_management/waitlists": { + "sdkMethod": "list_waitlists", + "service": "user_management" + }, + "GET /user_management/waitlists/{id}": { + "sdkMethod": "get_waitlist", + "service": "user_management" + }, + "GET /user_management/waitlists/{id}/entries": { + "sdkMethod": "list_waitlist_entries", + "service": "user_management" + }, + "POST /user_management/waitlists/{id}/entries": { + "sdkMethod": "create_waitlist_entry", + "service": "user_management" } } } diff --git a/src/workos/_client.py b/src/workos/_client.py index 41d319cc..9776dd23 100644 --- a/src/workos/_client.py +++ b/src/workos/_client.py @@ -5,41 +5,44 @@ import functools from ._base_client import ( - WorkOSClient as _SyncBase, AsyncWorkOSClient as _AsyncBase, ) +from ._base_client import ( + WorkOSClient as _SyncBase, +) +from .actions import Actions, AsyncActions +from .admin_portal._resource import AdminPortal, AsyncAdminPortal from .agents._resource import Agents, AsyncAgents -from .multi_factor_auth._resource import MultiFactorAuth, AsyncMultiFactorAuth -from .connect._resource import Connect, AsyncConnect -from .authorization._resource import Authorization, AsyncAuthorization -from .client_api._resource import ClientApi, AsyncClientApi -from .sso._resource import SSO, AsyncSSO -from .pipes._resource import Pipes, AsyncPipes -from .directory_sync._resource import DirectorySync, AsyncDirectorySync -from .events._resource import Events, AsyncEvents -from .feature_flags._resource import FeatureFlags, AsyncFeatureFlags +from .api_keys._resource import ApiKeys, AsyncApiKeys +from .audit_logs._resource import AsyncAuditLogs, AuditLogs +from .authorization._resource import AsyncAuthorization, Authorization +from .client_api._resource import AsyncClientApi, ClientApi +from .connect._resource import AsyncConnect, Connect +from .directory_sync._resource import AsyncDirectorySync, DirectorySync +from .events._resource import AsyncEvents, Events +from .feature_flags._resource import AsyncFeatureFlags, FeatureFlags +from .groups._resource import AsyncGroups, Groups +from .multi_factor_auth._resource import AsyncMultiFactorAuth, MultiFactorAuth from .organization_domains._resource import ( - OrganizationDomains, AsyncOrganizationDomains, + OrganizationDomains, ) -from .organizations._resource import Organizations, AsyncOrganizations -from .api_keys._resource import ApiKeys, AsyncApiKeys -from .pipes_provider._resource import PipesProvider, AsyncPipesProvider -from .groups._resource import Groups, AsyncGroups -from .admin_portal._resource import AdminPortal, AsyncAdminPortal -from .radar._resource import Radar, AsyncRadar -from .user_management._resource import UserManagement, AsyncUserManagement from .organization_membership._resource import ( - OrganizationMembershipService, AsyncOrganizationMembershipService, + OrganizationMembershipService, ) -from .vault._resource import Vault, AsyncVault -from .webhooks._resource import Webhooks, AsyncWebhooks -from .widgets._resource import Widgets, AsyncWidgets -from .audit_logs._resource import AuditLogs, AsyncAuditLogs +from .organizations._resource import AsyncOrganizations, Organizations from .passwordless import AsyncPasswordless, Passwordless -from .actions import Actions, AsyncActions +from .pipes._resource import AsyncPipes, Pipes +from .pipes_provider._resource import AsyncPipesProvider, PipesProvider from .pkce import PKCE +from .platform_teams._resource import AsyncPlatformTeams, PlatformTeams +from .radar._resource import AsyncRadar, Radar +from .sso._resource import SSO, AsyncSSO +from .user_management._resource import AsyncUserManagement, UserManagement +from .vault._resource import AsyncVault, Vault +from .webhooks._resource import AsyncWebhooks, Webhooks +from .widgets._resource import AsyncWidgets, Widgets class WorkOSClient(_SyncBase): @@ -120,6 +123,11 @@ def groups(self) -> Groups: """Groups API resources.""" return Groups(self) + @functools.cached_property + def platform_teams(self) -> PlatformTeams: + """Platform Teams API resources.""" + return PlatformTeams(self) + @functools.cached_property def admin_portal(self) -> AdminPortal: """Admin Portal API resources.""" @@ -263,6 +271,11 @@ def groups(self) -> AsyncGroups: """Groups API resources.""" return AsyncGroups(self) + @functools.cached_property + def platform_teams(self) -> AsyncPlatformTeams: + """Platform Teams API resources.""" + return AsyncPlatformTeams(self) + @functools.cached_property def admin_portal(self) -> AsyncAdminPortal: """Admin Portal API resources.""" diff --git a/src/workos/agents/_resource.py b/src/workos/agents/_resource.py index 7b6217c7..b512f857 100644 --- a/src/workos/agents/_resource.py +++ b/src/workos/agents/_resource.py @@ -10,12 +10,25 @@ from workos.common.models.agent_admin_validate_credential_request_type import ( AgentAdminValidateCredentialRequestType, ) +from workos.common.models.agent_blueprints_token_mint_token_request_type import ( + AgentBlueprintsTokenMintTokenRequestType, +) +from workos.common.models.pagination_order import PaginationOrder -from .._types import RequestOptions, enum_value +from .._pagination import AsyncPage, SyncPage +from .._types import NOT_GIVEN, NotGiven, RequestOptions, enum_value from .models import ( AgentAdminLinkClaimAttemptToExternalUserRequestUser, + AgentBlueprint, + AgentBlueprintsCreateRequestInvocableBy, + AgentBlueprintsCreateRequestSessionSetting, + AgentBlueprintsUpdateRequestInvocableBy, + AgentBlueprintsUpdateRequestSessionSetting, AgentCredentialValidation, + AgentInstance, + AgentInstanceSession, AgentRegistration, + AgentToken, ClaimViewResponse, ) @@ -26,78 +39,81 @@ class Agents: def __init__(self, client: WorkOSClient) -> None: self._client = client - def update_attempts( + def list_blueprints( self, *, - type: Literal["link_external_user"], - claim_attempt_token: str, - user: AgentAdminLinkClaimAttemptToExternalUserRequestUser, - organization_id: str | None = None, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", request_options: RequestOptions | None = None, - ) -> ClaimViewResponse: - """Link a claim attempt to an external user + ) -> SyncPage[AgentBlueprint]: + """List agent blueprints - Link an external user to a claim attempt and retrieve the code needed for the agent to complete the claim. The user is looked up by external ID; if no user exists, one is created. When the user belongs to multiple organizations, an explicit organization must be provided. + Lists the agent blueprints in the current environment. Args: - type: The operation to perform on the claim attempt. Currently only `link_external_user` is supported. - claim_attempt_token: The token identifying the claim attempt. - user: The user to attach to the claim attempt, identified by email and external ID. - organization_id: The organization to place the agent in. Required when the user belongs to more than one organization. + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: - ClaimViewResponse + SyncPage[AgentBlueprint] Raises: - BadRequestError: If the request is malformed (400). - AuthorizationError: If the request is forbidden (403). - ConflictError: If a conflict occurs (409). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ - body: dict[str, Any] = { + params = { k: v for k, v in { - "type": type, - "claim_attempt_token": claim_attempt_token, - "user": user.to_dict(), - "organization_id": organization_id, + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, }.items() if v is not None } - return self._client.request( - method="patch", - path=("agents", "claims", "attempts"), - body=body, - model=ClaimViewResponse, + return self._client.request_page( + method="get", + path=("agents", "blueprints"), + model=AgentBlueprint, + params=params, request_options=request_options, ) - def create_validate( + def create_blueprint( self, *, - type: AgentAdminValidateCredentialRequestType | str, - credential: str, - audience: str | None = None, + name: str, + session_settings: AgentBlueprintsCreateRequestSessionSetting, + description: str | None = None, + permissions: list[str] | None = None, + invocable_by: AgentBlueprintsCreateRequestInvocableBy | None = None, request_options: RequestOptions | None = None, - ) -> AgentCredentialValidation: - """Validate an agent credential + ) -> AgentBlueprint: + """Create an agent blueprint - Validate an agent credential — an API key or access token — against the environment of the API key used to authenticate the request. This is a read-only check: it never consumes or mutates the credential. + Creates an agent blueprint: the template describing what an agent may do (its permission ceiling), who may invoke it, and the lifetimes of its sessions. Args: - type: The kind of credential being validated — an agent API key or an agent access token. - credential: The credential value to validate: the API key value for `api_key`, or the access token (JWT) for `access_token`. - audience: When provided, the access token's `aud` claim is verified against this value. Tokens issued for a different resource are rejected. + name: Human-readable name of the agent blueprint. + description: Human-readable description of the agent blueprint. + permissions: Permission slugs forming the ceiling on what sessions minted from this blueprint may do. Each slug must exist in the environment. + invocable_by: Who may mint sessions from this blueprint. + session_settings: Token and session lifetimes for sessions minted from this blueprint. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: - AgentCredentialValidation + AgentBlueprint Raises: BadRequestError: If the request is malformed (400). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -105,36 +121,40 @@ def create_validate( body: dict[str, Any] = { k: v for k, v in { - "type": enum_value(type), - "credential": credential, - "audience": audience, + "name": name, + "description": description, + "permissions": permissions, + "invocable_by": invocable_by.to_dict() + if invocable_by is not None + else None, + "session_settings": session_settings.to_dict(), }.items() if v is not None } return self._client.request( method="post", - path=("agents", "credentials", "validate"), + path=("agents", "blueprints"), body=body, - model=AgentCredentialValidation, + model=AgentBlueprint, request_options=request_options, ) - def get_registration( + def get_blueprint( self, - id: str, + agent_blueprint_id: str, *, request_options: RequestOptions | None = None, - ) -> AgentRegistration: - """Get an agent registration + ) -> AgentBlueprint: + """Get an agent blueprint - Retrieve the details of an agent registration by ID. The registration is scoped to the environment of the API key used to authenticate the request. + Retrieves an agent blueprint by ID. Args: - id: The unique ID of the agent registration. + agent_blueprint_id: The unique ID of the agent blueprint. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: - AgentRegistration + AgentBlueprint Raises: NotFoundError: If the resource is not found (404). @@ -144,19 +164,155 @@ def get_registration( """ return self._client.request( method="get", - path=("agents", "registrations", str(id)), - model=AgentRegistration, + path=("agents", "blueprints", str(agent_blueprint_id)), + model=AgentBlueprint, request_options=request_options, ) + def update_blueprint( + self, + agent_blueprint_id: str, + *, + name: str | None = None, + description: str | None | NotGiven = NOT_GIVEN, + permissions: list[str] | None = None, + invocable_by: AgentBlueprintsUpdateRequestInvocableBy | None = None, + session_settings: AgentBlueprintsUpdateRequestSessionSetting | None = None, + request_options: RequestOptions | None = None, + ) -> AgentBlueprint: + """Update an agent blueprint -class AsyncAgents: - """Agents API resources (async).""" + Updates an agent blueprint. Omitted fields are left unchanged; provided lists replace the existing configuration. - def __init__(self, client: AsyncWorkOSClient) -> None: - self._client = client + Args: + agent_blueprint_id: The unique ID of the agent blueprint. + name: Human-readable name of the agent blueprint. + description: Human-readable description of the agent blueprint. Pass `null` to clear it. + permissions: Permission slugs forming the ceiling on what sessions minted from this blueprint may do. Each slug must exist in the environment. + invocable_by: Who may mint sessions from this blueprint. Omitted lists are left unchanged. + session_settings: Token and session lifetimes for sessions minted from this blueprint. Omitted fields are left unchanged. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - async def update_attempts( + Returns: + AgentBlueprint + + Raises: + BadRequestError: If the request is malformed (400). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "name": name, + "permissions": permissions, + "invocable_by": invocable_by.to_dict() + if invocable_by is not None + else None, + "session_settings": session_settings.to_dict() + if session_settings is not None + else None, + }.items() + if v is not None + } + if not isinstance(description, NotGiven): + body["description"] = description + return self._client.request( + method="patch", + path=("agents", "blueprints", str(agent_blueprint_id)), + body=body, + model=AgentBlueprint, + request_options=request_options, + ) + + def delete_blueprint( + self, + agent_blueprint_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an agent blueprint + + Deletes an agent blueprint along with its configuration, instances, and sessions. + + Args: + agent_blueprint_id: The unique ID of the agent blueprint. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="delete", + path=("agents", "blueprints", str(agent_blueprint_id)), + request_options=request_options, + ) + + def create_blueprint_token( + self, + agent_blueprint_id: str, + *, + type: AgentBlueprintsTokenMintTokenRequestType | str, + user_access_token: str | None = None, + intent: str | None = None, + organization_id: str | None = None, + agent_access_token: str | None = None, + refresh_token: str | None = None, + request_options: RequestOptions | None = None, + ) -> AgentToken: + """Mint an agent token + + Mint an agent access token (and backing session) from an agent blueprint. The session can be user-delegated (exchanging a user access token), autonomous (the agent acting as itself in an organization), agent-delegated (the agent exchanging its own access token for a new session on the same instance), or a refresh of a previously issued refresh token. + + Args: + agent_blueprint_id: The unique ID of the agent blueprint. + type: How the session is minted: `user_delegated`, `autonomous`, `agent_delegated`, or `refresh`. + user_access_token: The access token of the user delegating to the agent. The token identifies the user and organization; effective permissions are resolved server-side. + intent: Optional caller-supplied context, echoed as an object with a `text` field in the `intent` claim of the minted access token. + organization_id: The organization the agent acts within when operating as itself. + agent_access_token: The agent's own access token to exchange for a new session on the same instance. The token must have been minted from this blueprint; permissions are re-derived from current authority. + refresh_token: The refresh token issued with a previous agent access token. Refresh tokens are single-use: each refresh rotates it. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentToken + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "type": enum_value(type), + "user_access_token": user_access_token, + "intent": intent, + "organization_id": organization_id, + "agent_access_token": agent_access_token, + "refresh_token": refresh_token, + }.items() + if v is not None + } + return self._client.request( + method="post", + path=("agents", "blueprints", str(agent_blueprint_id), "tokens"), + body=body, + model=AgentToken, + request_options=request_options, + ) + + def update_attempts( self, *, type: Literal["link_external_user"], @@ -197,7 +353,7 @@ async def update_attempts( }.items() if v is not None } - return await self._client.request( + return self._client.request( method="patch", path=("agents", "claims", "attempts"), body=body, @@ -205,7 +361,7 @@ async def update_attempts( request_options=request_options, ) - async def create_validate( + def create_validate( self, *, type: AgentAdminValidateCredentialRequestType | str, @@ -241,7 +397,7 @@ async def create_validate( }.items() if v is not None } - return await self._client.request( + return self._client.request( method="post", path=("agents", "credentials", "validate"), body=body, @@ -249,7 +405,7 @@ async def create_validate( request_options=request_options, ) - async def get_registration( + def get_registration( self, id: str, *, @@ -272,9 +428,854 @@ async def get_registration( RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ - return await self._client.request( + return self._client.request( method="get", path=("agents", "registrations", str(id)), model=AgentRegistration, request_options=request_options, ) + + def list_instances( + self, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", + organization_id: str | None = None, + agent_blueprint_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> SyncPage[AgentInstance]: + """List agent instances + + Lists the agent instances in the current environment. Instances are created implicitly when tokens are minted. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + organization_id: Only return instances acting within this organization. + agent_blueprint_id: Only return instances minted from this blueprint. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SyncPage[AgentInstance] + + Raises: + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + "organization_id": organization_id, + "agent_blueprint_id": agent_blueprint_id, + }.items() + if v is not None + } + return self._client.request_page( + method="get", + path=("agents", "instances"), + model=AgentInstance, + params=params, + request_options=request_options, + ) + + def get_instance( + self, + agent_instance_id: str, + *, + request_options: RequestOptions | None = None, + ) -> AgentInstance: + """Get an agent instance + + Retrieves an agent instance by ID. + + Args: + agent_instance_id: The unique ID of the agent instance. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentInstance + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("agents", "instances", str(agent_instance_id)), + model=AgentInstance, + request_options=request_options, + ) + + def delete_instance( + self, + agent_instance_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an agent instance + + Deletes an agent instance along with its sessions, invalidating their refresh tokens. + + Args: + agent_instance_id: The unique ID of the agent instance. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="delete", + path=("agents", "instances", str(agent_instance_id)), + request_options=request_options, + ) + + def list_sessions( + self, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", + agent_blueprint_id: str | None = None, + agent_instance_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> SyncPage[AgentInstanceSession]: + """List agent instance sessions + + Lists the agent instance sessions in the current environment. Sessions are created when tokens are minted. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + agent_blueprint_id: Only return sessions of instances minted from this blueprint. + agent_instance_id: Only return sessions belonging to this agent instance. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SyncPage[AgentInstanceSession] + + Raises: + AuthenticationError: If the API key is invalid (401). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + "agent_blueprint_id": agent_blueprint_id, + "agent_instance_id": agent_instance_id, + }.items() + if v is not None + } + return self._client.request_page( + method="get", + path=("agents", "sessions"), + model=AgentInstanceSession, + params=params, + request_options=request_options, + ) + + def get_session( + self, + agent_instance_session_id: str, + *, + request_options: RequestOptions | None = None, + ) -> AgentInstanceSession: + """Get an agent instance session + + Retrieves an agent instance session by ID. + + Args: + agent_instance_session_id: The unique ID of the agent instance session. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentInstanceSession + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("agents", "sessions", str(agent_instance_session_id)), + model=AgentInstanceSession, + request_options=request_options, + ) + + def revoke_session( + self, + agent_instance_session_id: str, + *, + request_options: RequestOptions | None = None, + ) -> AgentInstanceSession: + """Revoke an agent instance session + + Revokes an agent instance session, invalidating its refresh token and every access token minted under it. Revocation is idempotent: revoking an already-revoked session keeps the original `revoked_at`, and revoking an already-expired session returns the session with `status: expired` and a null `revoked_at`. + + Args: + agent_instance_session_id: The unique ID of the agent instance session. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentInstanceSession + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="post", + path=("agents", "sessions", str(agent_instance_session_id), "revoke"), + model=AgentInstanceSession, + request_options=request_options, + ) + + +class AsyncAgents: + """Agents API resources (async).""" + + def __init__(self, client: AsyncWorkOSClient) -> None: + self._client = client + + async def list_blueprints( + self, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", + request_options: RequestOptions | None = None, + ) -> AsyncPage[AgentBlueprint]: + """List agent blueprints + + Lists the agent blueprints in the current environment. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AsyncPage[AgentBlueprint] + + Raises: + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + }.items() + if v is not None + } + return await self._client.request_page( + method="get", + path=("agents", "blueprints"), + model=AgentBlueprint, + params=params, + request_options=request_options, + ) + + async def create_blueprint( + self, + *, + name: str, + session_settings: AgentBlueprintsCreateRequestSessionSetting, + description: str | None = None, + permissions: list[str] | None = None, + invocable_by: AgentBlueprintsCreateRequestInvocableBy | None = None, + request_options: RequestOptions | None = None, + ) -> AgentBlueprint: + """Create an agent blueprint + + Creates an agent blueprint: the template describing what an agent may do (its permission ceiling), who may invoke it, and the lifetimes of its sessions. + + Args: + name: Human-readable name of the agent blueprint. + description: Human-readable description of the agent blueprint. + permissions: Permission slugs forming the ceiling on what sessions minted from this blueprint may do. Each slug must exist in the environment. + invocable_by: Who may mint sessions from this blueprint. + session_settings: Token and session lifetimes for sessions minted from this blueprint. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentBlueprint + + Raises: + BadRequestError: If the request is malformed (400). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "name": name, + "description": description, + "permissions": permissions, + "invocable_by": invocable_by.to_dict() + if invocable_by is not None + else None, + "session_settings": session_settings.to_dict(), + }.items() + if v is not None + } + return await self._client.request( + method="post", + path=("agents", "blueprints"), + body=body, + model=AgentBlueprint, + request_options=request_options, + ) + + async def get_blueprint( + self, + agent_blueprint_id: str, + *, + request_options: RequestOptions | None = None, + ) -> AgentBlueprint: + """Get an agent blueprint + + Retrieves an agent blueprint by ID. + + Args: + agent_blueprint_id: The unique ID of the agent blueprint. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentBlueprint + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("agents", "blueprints", str(agent_blueprint_id)), + model=AgentBlueprint, + request_options=request_options, + ) + + async def update_blueprint( + self, + agent_blueprint_id: str, + *, + name: str | None = None, + description: str | None | NotGiven = NOT_GIVEN, + permissions: list[str] | None = None, + invocable_by: AgentBlueprintsUpdateRequestInvocableBy | None = None, + session_settings: AgentBlueprintsUpdateRequestSessionSetting | None = None, + request_options: RequestOptions | None = None, + ) -> AgentBlueprint: + """Update an agent blueprint + + Updates an agent blueprint. Omitted fields are left unchanged; provided lists replace the existing configuration. + + Args: + agent_blueprint_id: The unique ID of the agent blueprint. + name: Human-readable name of the agent blueprint. + description: Human-readable description of the agent blueprint. Pass `null` to clear it. + permissions: Permission slugs forming the ceiling on what sessions minted from this blueprint may do. Each slug must exist in the environment. + invocable_by: Who may mint sessions from this blueprint. Omitted lists are left unchanged. + session_settings: Token and session lifetimes for sessions minted from this blueprint. Omitted fields are left unchanged. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentBlueprint + + Raises: + BadRequestError: If the request is malformed (400). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "name": name, + "permissions": permissions, + "invocable_by": invocable_by.to_dict() + if invocable_by is not None + else None, + "session_settings": session_settings.to_dict() + if session_settings is not None + else None, + }.items() + if v is not None + } + if not isinstance(description, NotGiven): + body["description"] = description + return await self._client.request( + method="patch", + path=("agents", "blueprints", str(agent_blueprint_id)), + body=body, + model=AgentBlueprint, + request_options=request_options, + ) + + async def delete_blueprint( + self, + agent_blueprint_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an agent blueprint + + Deletes an agent blueprint along with its configuration, instances, and sessions. + + Args: + agent_blueprint_id: The unique ID of the agent blueprint. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + await self._client.request( + method="delete", + path=("agents", "blueprints", str(agent_blueprint_id)), + request_options=request_options, + ) + + async def create_blueprint_token( + self, + agent_blueprint_id: str, + *, + type: AgentBlueprintsTokenMintTokenRequestType | str, + user_access_token: str | None = None, + intent: str | None = None, + organization_id: str | None = None, + agent_access_token: str | None = None, + refresh_token: str | None = None, + request_options: RequestOptions | None = None, + ) -> AgentToken: + """Mint an agent token + + Mint an agent access token (and backing session) from an agent blueprint. The session can be user-delegated (exchanging a user access token), autonomous (the agent acting as itself in an organization), agent-delegated (the agent exchanging its own access token for a new session on the same instance), or a refresh of a previously issued refresh token. + + Args: + agent_blueprint_id: The unique ID of the agent blueprint. + type: How the session is minted: `user_delegated`, `autonomous`, `agent_delegated`, or `refresh`. + user_access_token: The access token of the user delegating to the agent. The token identifies the user and organization; effective permissions are resolved server-side. + intent: Optional caller-supplied context, echoed as an object with a `text` field in the `intent` claim of the minted access token. + organization_id: The organization the agent acts within when operating as itself. + agent_access_token: The agent's own access token to exchange for a new session on the same instance. The token must have been minted from this blueprint; permissions are re-derived from current authority. + refresh_token: The refresh token issued with a previous agent access token. Refresh tokens are single-use: each refresh rotates it. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentToken + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "type": enum_value(type), + "user_access_token": user_access_token, + "intent": intent, + "organization_id": organization_id, + "agent_access_token": agent_access_token, + "refresh_token": refresh_token, + }.items() + if v is not None + } + return await self._client.request( + method="post", + path=("agents", "blueprints", str(agent_blueprint_id), "tokens"), + body=body, + model=AgentToken, + request_options=request_options, + ) + + async def update_attempts( + self, + *, + type: Literal["link_external_user"], + claim_attempt_token: str, + user: AgentAdminLinkClaimAttemptToExternalUserRequestUser, + organization_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> ClaimViewResponse: + """Link a claim attempt to an external user + + Link an external user to a claim attempt and retrieve the code needed for the agent to complete the claim. The user is looked up by external ID; if no user exists, one is created. When the user belongs to multiple organizations, an explicit organization must be provided. + + Args: + type: The operation to perform on the claim attempt. Currently only `link_external_user` is supported. + claim_attempt_token: The token identifying the claim attempt. + user: The user to attach to the claim attempt, identified by email and external ID. + organization_id: The organization to place the agent in. Required when the user belongs to more than one organization. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ClaimViewResponse + + Raises: + BadRequestError: If the request is malformed (400). + AuthorizationError: If the request is forbidden (403). + ConflictError: If a conflict occurs (409). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "type": type, + "claim_attempt_token": claim_attempt_token, + "user": user.to_dict(), + "organization_id": organization_id, + }.items() + if v is not None + } + return await self._client.request( + method="patch", + path=("agents", "claims", "attempts"), + body=body, + model=ClaimViewResponse, + request_options=request_options, + ) + + async def create_validate( + self, + *, + type: AgentAdminValidateCredentialRequestType | str, + credential: str, + audience: str | None = None, + request_options: RequestOptions | None = None, + ) -> AgentCredentialValidation: + """Validate an agent credential + + Validate an agent credential — an API key or access token — against the environment of the API key used to authenticate the request. This is a read-only check: it never consumes or mutates the credential. + + Args: + type: The kind of credential being validated — an agent API key or an agent access token. + credential: The credential value to validate: the API key value for `api_key`, or the access token (JWT) for `access_token`. + audience: When provided, the access token's `aud` claim is verified against this value. Tokens issued for a different resource are rejected. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentCredentialValidation + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "type": enum_value(type), + "credential": credential, + "audience": audience, + }.items() + if v is not None + } + return await self._client.request( + method="post", + path=("agents", "credentials", "validate"), + body=body, + model=AgentCredentialValidation, + request_options=request_options, + ) + + async def get_registration( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> AgentRegistration: + """Get an agent registration + + Retrieve the details of an agent registration by ID. The registration is scoped to the environment of the API key used to authenticate the request. + + Args: + id: The unique ID of the agent registration. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentRegistration + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("agents", "registrations", str(id)), + model=AgentRegistration, + request_options=request_options, + ) + + async def list_instances( + self, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", + organization_id: str | None = None, + agent_blueprint_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> AsyncPage[AgentInstance]: + """List agent instances + + Lists the agent instances in the current environment. Instances are created implicitly when tokens are minted. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + organization_id: Only return instances acting within this organization. + agent_blueprint_id: Only return instances minted from this blueprint. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AsyncPage[AgentInstance] + + Raises: + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + "organization_id": organization_id, + "agent_blueprint_id": agent_blueprint_id, + }.items() + if v is not None + } + return await self._client.request_page( + method="get", + path=("agents", "instances"), + model=AgentInstance, + params=params, + request_options=request_options, + ) + + async def get_instance( + self, + agent_instance_id: str, + *, + request_options: RequestOptions | None = None, + ) -> AgentInstance: + """Get an agent instance + + Retrieves an agent instance by ID. + + Args: + agent_instance_id: The unique ID of the agent instance. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentInstance + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("agents", "instances", str(agent_instance_id)), + model=AgentInstance, + request_options=request_options, + ) + + async def delete_instance( + self, + agent_instance_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an agent instance + + Deletes an agent instance along with its sessions, invalidating their refresh tokens. + + Args: + agent_instance_id: The unique ID of the agent instance. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + await self._client.request( + method="delete", + path=("agents", "instances", str(agent_instance_id)), + request_options=request_options, + ) + + async def list_sessions( + self, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", + agent_blueprint_id: str | None = None, + agent_instance_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> AsyncPage[AgentInstanceSession]: + """List agent instance sessions + + Lists the agent instance sessions in the current environment. Sessions are created when tokens are minted. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + agent_blueprint_id: Only return sessions of instances minted from this blueprint. + agent_instance_id: Only return sessions belonging to this agent instance. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AsyncPage[AgentInstanceSession] + + Raises: + AuthenticationError: If the API key is invalid (401). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + "agent_blueprint_id": agent_blueprint_id, + "agent_instance_id": agent_instance_id, + }.items() + if v is not None + } + return await self._client.request_page( + method="get", + path=("agents", "sessions"), + model=AgentInstanceSession, + params=params, + request_options=request_options, + ) + + async def get_session( + self, + agent_instance_session_id: str, + *, + request_options: RequestOptions | None = None, + ) -> AgentInstanceSession: + """Get an agent instance session + + Retrieves an agent instance session by ID. + + Args: + agent_instance_session_id: The unique ID of the agent instance session. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentInstanceSession + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("agents", "sessions", str(agent_instance_session_id)), + model=AgentInstanceSession, + request_options=request_options, + ) + + async def revoke_session( + self, + agent_instance_session_id: str, + *, + request_options: RequestOptions | None = None, + ) -> AgentInstanceSession: + """Revoke an agent instance session + + Revokes an agent instance session, invalidating its refresh token and every access token minted under it. Revocation is idempotent: revoking an already-revoked session keeps the original `revoked_at`, and revoking an already-expired session returns the session with `status: expired` and a null `revoked_at`. + + Args: + agent_instance_session_id: The unique ID of the agent instance session. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AgentInstanceSession + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="post", + path=("agents", "sessions", str(agent_instance_session_id), "revoke"), + model=AgentInstanceSession, + request_options=request_options, + ) diff --git a/src/workos/agents/models/__init__.py b/src/workos/agents/models/__init__.py index a176f403..d31ad740 100644 --- a/src/workos/agents/models/__init__.py +++ b/src/workos/agents/models/__init__.py @@ -1,5 +1,7 @@ # This file is auto-generated by oagen. Do not edit. +from workos.common.models.pagination_order import PaginationOrder as PaginationOrder + from .agent_admin_link_claim_attempt_to_external_user_request import ( AgentAdminLinkClaimAttemptToExternalUserRequest as AgentAdminLinkClaimAttemptToExternalUserRequest, ) @@ -9,9 +11,39 @@ from .agent_admin_validate_credential_request import ( AgentAdminValidateCredentialRequest as AgentAdminValidateCredentialRequest, ) +from .agent_blueprint import AgentBlueprint as AgentBlueprint +from .agent_blueprint_invocable_by import ( + AgentBlueprintInvocableBy as AgentBlueprintInvocableBy, +) +from .agent_blueprint_session_setting import ( + AgentBlueprintSessionSetting as AgentBlueprintSessionSetting, +) +from .agent_blueprints_create_request import ( + AgentBlueprintsCreateRequest as AgentBlueprintsCreateRequest, +) +from .agent_blueprints_create_request_invocable_by import ( + AgentBlueprintsCreateRequestInvocableBy as AgentBlueprintsCreateRequestInvocableBy, +) +from .agent_blueprints_create_request_session_setting import ( + AgentBlueprintsCreateRequestSessionSetting as AgentBlueprintsCreateRequestSessionSetting, +) +from .agent_blueprints_token_mint_token_request import ( + AgentBlueprintsTokenMintTokenRequest as AgentBlueprintsTokenMintTokenRequest, +) +from .agent_blueprints_update_request import ( + AgentBlueprintsUpdateRequest as AgentBlueprintsUpdateRequest, +) +from .agent_blueprints_update_request_invocable_by import ( + AgentBlueprintsUpdateRequestInvocableBy as AgentBlueprintsUpdateRequestInvocableBy, +) +from .agent_blueprints_update_request_session_setting import ( + AgentBlueprintsUpdateRequestSessionSetting as AgentBlueprintsUpdateRequestSessionSetting, +) from .agent_credential_validation import ( AgentCredentialValidation as AgentCredentialValidation, ) +from .agent_instance import AgentInstance as AgentInstance +from .agent_instance_session import AgentInstanceSession as AgentInstanceSession from .agent_registration import AgentRegistration as AgentRegistration from .agent_registration_agent_identity import ( AgentRegistrationAgentIdentity as AgentRegistrationAgentIdentity, @@ -20,6 +52,7 @@ from .agent_registration_claim_claim_completion import ( AgentRegistrationClaimClaimCompletion as AgentRegistrationClaimClaimCompletion, ) +from .agent_token import AgentToken as AgentToken from .claim_view_response import ClaimViewResponse as ClaimViewResponse from .claim_view_response_organization import ( ClaimViewResponseOrganization as ClaimViewResponseOrganization, diff --git a/src/workos/agents/models/agent_blueprint.py b/src/workos/agents/models/agent_blueprint.py new file mode 100644 index 00000000..60631320 --- /dev/null +++ b/src/workos/agents/models/agent_blueprint.py @@ -0,0 +1,75 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .agent_blueprint_invocable_by import AgentBlueprintInvocableBy +from .agent_blueprint_session_setting import AgentBlueprintSessionSetting + + +@dataclass(slots=True) +class AgentBlueprint: + """Agent Blueprint model.""" + + object: Literal["agent_blueprint"] + """Distinguishes the agent blueprint object.""" + id: str + """Unique identifier of the agent blueprint.""" + name: str + """Human-readable name of the agent blueprint.""" + description: str | None + """Human-readable description of the agent blueprint.""" + permissions: list[str] + """Permission slugs forming the ceiling on what sessions minted from this blueprint may do.""" + invocable_by: AgentBlueprintInvocableBy + """Who may mint sessions from this blueprint.""" + session_settings: AgentBlueprintSessionSetting + """Token and session lifetimes for sessions minted from this blueprint.""" + created_at: datetime + """Timestamp when the agent blueprint was created.""" + updated_at: datetime + """Timestamp when the agent blueprint was last updated.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprint: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "agent_blueprint"), + id=data["id"], + name=data["name"], + description=data["description"], + permissions=data["permissions"], + invocable_by=AgentBlueprintInvocableBy.from_dict( + cast(dict[str, Any], data["invocable_by"]) + ), + session_settings=AgentBlueprintSessionSetting.from_dict( + cast(dict[str, Any], data["session_settings"]) + ), + created_at=_parse_datetime(data["created_at"]), + updated_at=_parse_datetime(data["updated_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprint", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["name"] = self.name + if self.description is not None: + result["description"] = self.description + else: + result["description"] = None + result["permissions"] = self.permissions + result["invocable_by"] = self.invocable_by.to_dict() + result["session_settings"] = self.session_settings.to_dict() + result["created_at"] = _format_datetime(self.created_at) + result["updated_at"] = _format_datetime(self.updated_at) + return result diff --git a/src/workos/agents/models/agent_blueprint_invocable_by.py b/src/workos/agents/models/agent_blueprint_invocable_by.py new file mode 100644 index 00000000..252701a1 --- /dev/null +++ b/src/workos/agents/models/agent_blueprint_invocable_by.py @@ -0,0 +1,9 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from workos.common.models.agent_blueprint_created_data_invocable_by import ( + AgentBlueprintCreatedDataInvocableBy, +) + +AgentBlueprintInvocableBy: TypeAlias = AgentBlueprintCreatedDataInvocableBy diff --git a/src/workos/agents/models/agent_blueprint_session_setting.py b/src/workos/agents/models/agent_blueprint_session_setting.py new file mode 100644 index 00000000..9e41a83c --- /dev/null +++ b/src/workos/agents/models/agent_blueprint_session_setting.py @@ -0,0 +1,9 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .agent_blueprints_create_request_session_setting import ( + AgentBlueprintsCreateRequestSessionSetting, +) + +AgentBlueprintSessionSetting: TypeAlias = AgentBlueprintsCreateRequestSessionSetting diff --git a/src/workos/agents/models/agent_blueprints_create_request.py b/src/workos/agents/models/agent_blueprints_create_request.py new file mode 100644 index 00000000..41a1b456 --- /dev/null +++ b/src/workos/agents/models/agent_blueprints_create_request.py @@ -0,0 +1,64 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, cast + +from workos._types import _raise_deserialize_error + +from .agent_blueprints_create_request_invocable_by import ( + AgentBlueprintsCreateRequestInvocableBy, +) +from .agent_blueprints_create_request_session_setting import ( + AgentBlueprintsCreateRequestSessionSetting, +) + + +@dataclass(slots=True) +class AgentBlueprintsCreateRequest: + """Agent Blueprints Create Request model.""" + + name: str + """Human-readable name of the agent blueprint.""" + session_settings: AgentBlueprintsCreateRequestSessionSetting + """Token and session lifetimes for sessions minted from this blueprint.""" + description: str | None = None + """Human-readable description of the agent blueprint.""" + permissions: list[str] | None = None + """Permission slugs forming the ceiling on what sessions minted from this blueprint may do. Each slug must exist in the environment.""" + invocable_by: AgentBlueprintsCreateRequestInvocableBy | None = None + """Who may mint sessions from this blueprint.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintsCreateRequest: + """Deserialize from a dictionary.""" + try: + return cls( + name=data["name"], + session_settings=AgentBlueprintsCreateRequestSessionSetting.from_dict( + cast(dict[str, Any], data["session_settings"]) + ), + description=data.get("description"), + permissions=data.get("permissions"), + invocable_by=AgentBlueprintsCreateRequestInvocableBy.from_dict( + cast(dict[str, Any], _v_invocable_by) + ) + if (_v_invocable_by := data.get("invocable_by")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintsCreateRequest", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["name"] = self.name + result["session_settings"] = self.session_settings.to_dict() + if self.description is not None: + result["description"] = self.description + if self.permissions is not None: + result["permissions"] = self.permissions + if self.invocable_by is not None: + result["invocable_by"] = self.invocable_by.to_dict() + return result diff --git a/src/workos/agents/models/agent_blueprints_create_request_invocable_by.py b/src/workos/agents/models/agent_blueprints_create_request_invocable_by.py new file mode 100644 index 00000000..a825b716 --- /dev/null +++ b/src/workos/agents/models/agent_blueprints_create_request_invocable_by.py @@ -0,0 +1,38 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AgentBlueprintsCreateRequestInvocableBy: + """Who may mint sessions from this blueprint.""" + + role_slugs: list[str] | None = None + """Role slugs whose members may mint user-delegated sessions from this blueprint. An empty list allows any member.""" + organization_ids: list[str] | None = None + """Organizations in which sessions may be minted from this blueprint, enforced on user-delegated, autonomous, and agent-delegated mints. An empty list allows any organization in the environment.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintsCreateRequestInvocableBy: + """Deserialize from a dictionary.""" + try: + return cls( + role_slugs=data.get("role_slugs"), + organization_ids=data.get("organization_ids"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintsCreateRequestInvocableBy", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.role_slugs is not None: + result["role_slugs"] = self.role_slugs + if self.organization_ids is not None: + result["organization_ids"] = self.organization_ids + return result diff --git a/src/workos/agents/models/agent_blueprints_create_request_session_setting.py b/src/workos/agents/models/agent_blueprints_create_request_session_setting.py new file mode 100644 index 00000000..018df05c --- /dev/null +++ b/src/workos/agents/models/agent_blueprints_create_request_session_setting.py @@ -0,0 +1,42 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AgentBlueprintsCreateRequestSessionSetting: + """Token and session lifetimes for sessions minted from this blueprint.""" + + max_age_seconds: int + """Maximum lifetime of a session in seconds; refreshes never extend a session past this. At most 31,536,000 (365 days).""" + access_token_ttl_seconds: int + """Lifetime of each minted access token in seconds. At most 3,600 (1 hour).""" + refresh_token_ttl_seconds: int + """Lifetime of each rotated refresh token in seconds. At most 5,184,000 (60 days).""" + + @classmethod + def from_dict( + cls, data: dict[str, Any] + ) -> AgentBlueprintsCreateRequestSessionSetting: + """Deserialize from a dictionary.""" + try: + return cls( + max_age_seconds=data["max_age_seconds"], + access_token_ttl_seconds=data["access_token_ttl_seconds"], + refresh_token_ttl_seconds=data["refresh_token_ttl_seconds"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintsCreateRequestSessionSetting", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["max_age_seconds"] = self.max_age_seconds + result["access_token_ttl_seconds"] = self.access_token_ttl_seconds + result["refresh_token_ttl_seconds"] = self.refresh_token_ttl_seconds + return result diff --git a/src/workos/agents/models/agent_blueprints_token_mint_token_request.py b/src/workos/agents/models/agent_blueprints_token_mint_token_request.py new file mode 100644 index 00000000..06ebe57a --- /dev/null +++ b/src/workos/agents/models/agent_blueprints_token_mint_token_request.py @@ -0,0 +1,61 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any + +from workos._types import _raise_deserialize_error +from workos.common.models.agent_blueprints_token_mint_token_request_type import ( + AgentBlueprintsTokenMintTokenRequestType, +) + + +@dataclass(slots=True) +class AgentBlueprintsTokenMintTokenRequest: + """Agent Blueprints Token Mint Token Request model.""" + + type: AgentBlueprintsTokenMintTokenRequestType + """How the session is minted: `user_delegated`, `autonomous`, `agent_delegated`, or `refresh`.""" + user_access_token: str | None = None + """The access token of the user delegating to the agent. The token identifies the user and organization; effective permissions are resolved server-side.""" + intent: str | None = None + """Optional caller-supplied context, echoed as an object with a `text` field in the `intent` claim of the minted access token.""" + organization_id: str | None = None + """The organization the agent acts within when operating as itself.""" + agent_access_token: str | None = None + """The agent's own access token to exchange for a new session on the same instance. The token must have been minted from this blueprint; permissions are re-derived from current authority.""" + refresh_token: str | None = None + """The refresh token issued with a previous agent access token. Refresh tokens are single-use: each refresh rotates it.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintsTokenMintTokenRequest: + """Deserialize from a dictionary.""" + try: + return cls( + type=AgentBlueprintsTokenMintTokenRequestType(data["type"]), + user_access_token=data.get("user_access_token"), + intent=data.get("intent"), + organization_id=data.get("organization_id"), + agent_access_token=data.get("agent_access_token"), + refresh_token=data.get("refresh_token"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintsTokenMintTokenRequest", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["type"] = self.type.value if isinstance(self.type, Enum) else self.type + if self.user_access_token is not None: + result["user_access_token"] = self.user_access_token + if self.intent is not None: + result["intent"] = self.intent + if self.organization_id is not None: + result["organization_id"] = self.organization_id + if self.agent_access_token is not None: + result["agent_access_token"] = self.agent_access_token + if self.refresh_token is not None: + result["refresh_token"] = self.refresh_token + return result diff --git a/src/workos/agents/models/agent_blueprints_update_request.py b/src/workos/agents/models/agent_blueprints_update_request.py new file mode 100644 index 00000000..4534d88f --- /dev/null +++ b/src/workos/agents/models/agent_blueprints_update_request.py @@ -0,0 +1,70 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, cast + +from workos._types import _raise_deserialize_error + +from .agent_blueprints_update_request_invocable_by import ( + AgentBlueprintsUpdateRequestInvocableBy, +) +from .agent_blueprints_update_request_session_setting import ( + AgentBlueprintsUpdateRequestSessionSetting, +) + + +@dataclass(slots=True) +class AgentBlueprintsUpdateRequest: + """Agent Blueprints Update Request model.""" + + name: str | None = None + """Human-readable name of the agent blueprint.""" + description: str | None = None + """Human-readable description of the agent blueprint. Pass `null` to clear it.""" + permissions: list[str] | None = None + """Permission slugs forming the ceiling on what sessions minted from this blueprint may do. Each slug must exist in the environment.""" + invocable_by: AgentBlueprintsUpdateRequestInvocableBy | None = None + """Who may mint sessions from this blueprint. Omitted lists are left unchanged.""" + session_settings: AgentBlueprintsUpdateRequestSessionSetting | None = None + """Token and session lifetimes for sessions minted from this blueprint. Omitted fields are left unchanged.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintsUpdateRequest: + """Deserialize from a dictionary.""" + try: + return cls( + name=data.get("name"), + description=data.get("description"), + permissions=data.get("permissions"), + invocable_by=AgentBlueprintsUpdateRequestInvocableBy.from_dict( + cast(dict[str, Any], _v_invocable_by) + ) + if (_v_invocable_by := data.get("invocable_by")) is not None + else None, + session_settings=AgentBlueprintsUpdateRequestSessionSetting.from_dict( + cast(dict[str, Any], _v_session_settings) + ) + if (_v_session_settings := data.get("session_settings")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintsUpdateRequest", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.name is not None: + result["name"] = self.name + if self.description is not None: + result["description"] = self.description + else: + result["description"] = None + if self.permissions is not None: + result["permissions"] = self.permissions + if self.invocable_by is not None: + result["invocable_by"] = self.invocable_by.to_dict() + if self.session_settings is not None: + result["session_settings"] = self.session_settings.to_dict() + return result diff --git a/src/workos/agents/models/agent_blueprints_update_request_invocable_by.py b/src/workos/agents/models/agent_blueprints_update_request_invocable_by.py new file mode 100644 index 00000000..b9148c96 --- /dev/null +++ b/src/workos/agents/models/agent_blueprints_update_request_invocable_by.py @@ -0,0 +1,11 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .agent_blueprints_create_request_invocable_by import ( + AgentBlueprintsCreateRequestInvocableBy, +) + +AgentBlueprintsUpdateRequestInvocableBy: TypeAlias = ( + AgentBlueprintsCreateRequestInvocableBy +) diff --git a/src/workos/agents/models/agent_blueprints_update_request_session_setting.py b/src/workos/agents/models/agent_blueprints_update_request_session_setting.py new file mode 100644 index 00000000..5a551736 --- /dev/null +++ b/src/workos/agents/models/agent_blueprints_update_request_session_setting.py @@ -0,0 +1,45 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AgentBlueprintsUpdateRequestSessionSetting: + """Token and session lifetimes for sessions minted from this blueprint. Omitted fields are left unchanged.""" + + max_age_seconds: int | None = None + """Maximum lifetime of a session in seconds; refreshes never extend a session past this. At most 31,536,000 (365 days).""" + access_token_ttl_seconds: int | None = None + """Lifetime of each minted access token in seconds. At most 3,600 (1 hour).""" + refresh_token_ttl_seconds: int | None = None + """Lifetime of each rotated refresh token in seconds. At most 5,184,000 (60 days).""" + + @classmethod + def from_dict( + cls, data: dict[str, Any] + ) -> AgentBlueprintsUpdateRequestSessionSetting: + """Deserialize from a dictionary.""" + try: + return cls( + max_age_seconds=data.get("max_age_seconds"), + access_token_ttl_seconds=data.get("access_token_ttl_seconds"), + refresh_token_ttl_seconds=data.get("refresh_token_ttl_seconds"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintsUpdateRequestSessionSetting", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.max_age_seconds is not None: + result["max_age_seconds"] = self.max_age_seconds + if self.access_token_ttl_seconds is not None: + result["access_token_ttl_seconds"] = self.access_token_ttl_seconds + if self.refresh_token_ttl_seconds is not None: + result["refresh_token_ttl_seconds"] = self.refresh_token_ttl_seconds + return result diff --git a/src/workos/agents/models/agent_instance.py b/src/workos/agents/models/agent_instance.py new file mode 100644 index 00000000..daea01cf --- /dev/null +++ b/src/workos/agents/models/agent_instance.py @@ -0,0 +1,66 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from enum import Enum +from typing import Any, Literal + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error +from workos.common.models.agent_instance_type import AgentInstanceType + + +@dataclass(slots=True) +class AgentInstance: + """Agent Instance model.""" + + object: Literal["agent_instance"] + """Distinguishes the agent instance object.""" + id: str + """Unique identifier of the agent instance.""" + agent_blueprint_id: str + """The blueprint this instance was minted from.""" + organization_id: str + """The organization the instance acts within.""" + organization_membership_id: str | None + """The organization membership of the delegating user; `null` for autonomous instances.""" + type: AgentInstanceType + """Whether the instance acts on behalf of a specific user (`delegated`) or as itself (`autonomous`).""" + created_at: datetime + """Timestamp when the agent instance was created.""" + updated_at: datetime + """Timestamp when the agent instance was last updated.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentInstance: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "agent_instance"), + id=data["id"], + agent_blueprint_id=data["agent_blueprint_id"], + organization_id=data["organization_id"], + organization_membership_id=data["organization_membership_id"], + type=AgentInstanceType(data["type"]), + created_at=_parse_datetime(data["created_at"]), + updated_at=_parse_datetime(data["updated_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentInstance", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["agent_blueprint_id"] = self.agent_blueprint_id + result["organization_id"] = self.organization_id + if self.organization_membership_id is not None: + result["organization_membership_id"] = self.organization_membership_id + else: + result["organization_membership_id"] = None + result["type"] = self.type.value if isinstance(self.type, Enum) else self.type + result["created_at"] = _format_datetime(self.created_at) + result["updated_at"] = _format_datetime(self.updated_at) + return result diff --git a/src/workos/agents/models/agent_instance_session.py b/src/workos/agents/models/agent_instance_session.py new file mode 100644 index 00000000..ad2d28a0 --- /dev/null +++ b/src/workos/agents/models/agent_instance_session.py @@ -0,0 +1,72 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from enum import Enum +from typing import Any, Literal + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error +from workos.common.models.agent_instance_session_status import ( + AgentInstanceSessionStatus, +) + + +@dataclass(slots=True) +class AgentInstanceSession: + """Agent Instance Session model.""" + + object: Literal["agent_instance_session"] + """Distinguishes the agent instance session object.""" + id: str + """Unique identifier of the agent instance session.""" + agent_instance_id: str + """The agent instance the session belongs to.""" + status: AgentInstanceSessionStatus + """Derived from `revoked_at` and `expires_at` at read time; a revoked session stays `revoked` even after it expires.""" + expires_at: datetime + """Timestamp when the session expires.""" + revoked_at: datetime | None + """Timestamp when the session was revoked; `null` if it has not been revoked.""" + created_at: datetime + """Timestamp when the session was created.""" + updated_at: datetime + """Timestamp when the session was last updated.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentInstanceSession: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "agent_instance_session"), + id=data["id"], + agent_instance_id=data["agent_instance_id"], + status=AgentInstanceSessionStatus(data["status"]), + expires_at=_parse_datetime(data["expires_at"]), + revoked_at=_parse_datetime(_v_revoked_at) + if (_v_revoked_at := data["revoked_at"]) is not None + else None, + created_at=_parse_datetime(data["created_at"]), + updated_at=_parse_datetime(data["updated_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentInstanceSession", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["agent_instance_id"] = self.agent_instance_id + result["status"] = ( + self.status.value if isinstance(self.status, Enum) else self.status + ) + result["expires_at"] = _format_datetime(self.expires_at) + if self.revoked_at is not None: + result["revoked_at"] = _format_datetime(self.revoked_at) + else: + result["revoked_at"] = None + result["created_at"] = _format_datetime(self.created_at) + result["updated_at"] = _format_datetime(self.updated_at) + return result diff --git a/src/workos/agents/models/agent_token.py b/src/workos/agents/models/agent_token.py new file mode 100644 index 00000000..8d7b4236 --- /dev/null +++ b/src/workos/agents/models/agent_token.py @@ -0,0 +1,60 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AgentToken: + """Agent Token model.""" + + access_token: str + """The agent access token (a JWT) carrying the effective permissions.""" + token_type: Literal["Bearer"] + """Always `Bearer`.""" + expires_in: int + """Number of seconds until the access token expires.""" + refresh_token: str + """Single-use refresh token for rotating the access token within the session lifetime.""" + agent_instance_id: str + """The agent instance the session belongs to.""" + new_instance: bool + """Whether this mint created the agent instance: `true` only for the mint that inserted the row, `false` when an existing instance was reused (including when a concurrent mint inserted it first).""" + agent_instance_session_id: str + """The backing agent instance session.""" + permissions: list[str] + """The effective permission slugs carried by the token.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentToken: + """Deserialize from a dictionary.""" + try: + return cls( + access_token=data["access_token"], + token_type=data.get("token_type", "Bearer"), + expires_in=data["expires_in"], + refresh_token=data["refresh_token"], + agent_instance_id=data["agent_instance_id"], + new_instance=data["new_instance"], + agent_instance_session_id=data["agent_instance_session_id"], + permissions=data["permissions"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentToken", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["access_token"] = self.access_token + result["token_type"] = self.token_type + result["expires_in"] = self.expires_in + result["refresh_token"] = self.refresh_token + result["agent_instance_id"] = self.agent_instance_id + result["new_instance"] = self.new_instance + result["agent_instance_session_id"] = self.agent_instance_session_id + result["permissions"] = self.permissions + return result diff --git a/src/workos/authorization/_resource.py b/src/workos/authorization/_resource.py index d5aa7666..d654d493 100644 --- a/src/workos/authorization/_resource.py +++ b/src/workos/authorization/_resource.py @@ -479,7 +479,7 @@ def list_effective_permissions( ) -> SyncPage[AuthorizationPermission]: """List effective permissions for an organization membership on a resource - Returns all permissions the organization membership effectively has on a resource, including permissions inherited through roles assigned to ancestor resources. + Returns all permissions the organization membership effectively has on a resource, including permissions inherited through roles assigned to ancestor resources. Results are not filtered by the resource type: a permission is returned whenever a check for it on this resource would be authorized, and each permission is labeled with the resource type it is declared on. Args: organization_membership_id: The ID of the organization membership. @@ -540,7 +540,7 @@ def list_effective_permissions_by_external_id( ) -> SyncPage[AuthorizationPermission]: """List effective permissions for an organization membership on a resource by external ID - Returns all permissions the organization membership effectively has on a resource identified by its external ID, including permissions inherited through roles assigned to ancestor resources. + Returns all permissions the organization membership effectively has on a resource identified by its external ID, including permissions inherited through roles assigned to ancestor resources. Results are not filtered by the resource type: a permission is returned whenever a check for it on this resource would be authorized, and each permission is labeled with the resource type it is declared on. Args: organization_membership_id: The ID of the organization membership. @@ -1226,12 +1226,12 @@ def update_resource_by_external_id( if isinstance(parent_resource, ParentResourceById): body["parent_resource_id"] = parent_resource.parent_resource_id elif isinstance(parent_resource, ParentResourceByExternalId): - body["parent_resource_external_id"] = ( - parent_resource.parent_resource_external_id - ) body["parent_resource_type_slug"] = ( parent_resource.parent_resource_type_slug ) + body["parent_resource_external_id"] = ( + parent_resource.parent_resource_external_id + ) return self._client.request( method="patch", path=( @@ -1549,12 +1549,12 @@ def create_resource( if isinstance(parent_resource, ParentResourceById): body["parent_resource_id"] = parent_resource.parent_resource_id elif isinstance(parent_resource, ParentResourceByExternalId): - body["parent_resource_external_id"] = ( - parent_resource.parent_resource_external_id - ) body["parent_resource_type_slug"] = ( parent_resource.parent_resource_type_slug ) + body["parent_resource_external_id"] = ( + parent_resource.parent_resource_external_id + ) return self._client.request( method="post", path=("authorization", "resources"), @@ -1641,12 +1641,12 @@ def update_resource( if isinstance(parent_resource, ParentResourceById): body["parent_resource_id"] = parent_resource.parent_resource_id elif isinstance(parent_resource, ParentResourceByExternalId): - body["parent_resource_external_id"] = ( - parent_resource.parent_resource_external_id - ) body["parent_resource_type_slug"] = ( parent_resource.parent_resource_type_slug ) + body["parent_resource_external_id"] = ( + parent_resource.parent_resource_external_id + ) return self._client.request( method="patch", path=("authorization", "resources", str(resource_id)), @@ -2650,7 +2650,7 @@ async def list_effective_permissions( ) -> AsyncPage[AuthorizationPermission]: """List effective permissions for an organization membership on a resource - Returns all permissions the organization membership effectively has on a resource, including permissions inherited through roles assigned to ancestor resources. + Returns all permissions the organization membership effectively has on a resource, including permissions inherited through roles assigned to ancestor resources. Results are not filtered by the resource type: a permission is returned whenever a check for it on this resource would be authorized, and each permission is labeled with the resource type it is declared on. Args: organization_membership_id: The ID of the organization membership. @@ -2711,7 +2711,7 @@ async def list_effective_permissions_by_external_id( ) -> AsyncPage[AuthorizationPermission]: """List effective permissions for an organization membership on a resource by external ID - Returns all permissions the organization membership effectively has on a resource identified by its external ID, including permissions inherited through roles assigned to ancestor resources. + Returns all permissions the organization membership effectively has on a resource identified by its external ID, including permissions inherited through roles assigned to ancestor resources. Results are not filtered by the resource type: a permission is returned whenever a check for it on this resource would be authorized, and each permission is labeled with the resource type it is declared on. Args: organization_membership_id: The ID of the organization membership. @@ -3397,12 +3397,12 @@ async def update_resource_by_external_id( if isinstance(parent_resource, ParentResourceById): body["parent_resource_id"] = parent_resource.parent_resource_id elif isinstance(parent_resource, ParentResourceByExternalId): - body["parent_resource_external_id"] = ( - parent_resource.parent_resource_external_id - ) body["parent_resource_type_slug"] = ( parent_resource.parent_resource_type_slug ) + body["parent_resource_external_id"] = ( + parent_resource.parent_resource_external_id + ) return await self._client.request( method="patch", path=( @@ -3720,12 +3720,12 @@ async def create_resource( if isinstance(parent_resource, ParentResourceById): body["parent_resource_id"] = parent_resource.parent_resource_id elif isinstance(parent_resource, ParentResourceByExternalId): - body["parent_resource_external_id"] = ( - parent_resource.parent_resource_external_id - ) body["parent_resource_type_slug"] = ( parent_resource.parent_resource_type_slug ) + body["parent_resource_external_id"] = ( + parent_resource.parent_resource_external_id + ) return await self._client.request( method="post", path=("authorization", "resources"), @@ -3812,12 +3812,12 @@ async def update_resource( if isinstance(parent_resource, ParentResourceById): body["parent_resource_id"] = parent_resource.parent_resource_id elif isinstance(parent_resource, ParentResourceByExternalId): - body["parent_resource_external_id"] = ( - parent_resource.parent_resource_external_id - ) body["parent_resource_type_slug"] = ( parent_resource.parent_resource_type_slug ) + body["parent_resource_external_id"] = ( + parent_resource.parent_resource_external_id + ) return await self._client.request( method="patch", path=("authorization", "resources", str(resource_id)), diff --git a/src/workos/authorization/models/__init__.py b/src/workos/authorization/models/__init__.py index 5251b450..3923b6cd 100644 --- a/src/workos/authorization/models/__init__.py +++ b/src/workos/authorization/models/__init__.py @@ -7,7 +7,6 @@ AuthorizationPermission as AuthorizationPermission, ) from workos.common.models.list_metadata import ListMetadata as ListMetadata -from workos.common.models.pagination_order import PaginationOrder as PaginationOrder from workos.common.models.slim_role import SlimRole as SlimRole from .assign_role import AssignRole as AssignRole diff --git a/src/workos/common/__init__.py b/src/workos/common/__init__.py index 563d82e2..df880899 100644 --- a/src/workos/common/__init__.py +++ b/src/workos/common/__init__.py @@ -13,6 +13,37 @@ from .models import ( AgentAdminValidateCredentialRequestType as AgentAdminValidateCredentialRequestType, ) +from .models import AgentBlueprintCreated as AgentBlueprintCreated +from .models import AgentBlueprintCreatedData as AgentBlueprintCreatedData +from .models import ( + AgentBlueprintCreatedDataInvocableBy as AgentBlueprintCreatedDataInvocableBy, +) +from .models import ( + AgentBlueprintCreatedDataSessionSetting as AgentBlueprintCreatedDataSessionSetting, +) +from .models import AgentBlueprintDeleted as AgentBlueprintDeleted +from .models import AgentBlueprintDeletedData as AgentBlueprintDeletedData +from .models import ( + AgentBlueprintsTokenMintTokenRequestType as AgentBlueprintsTokenMintTokenRequestType, +) +from .models import AgentBlueprintUpdated as AgentBlueprintUpdated +from .models import AgentBlueprintUpdatedData as AgentBlueprintUpdatedData +from .models import ( + AgentBlueprintUpdatedDataInvocableBy as AgentBlueprintUpdatedDataInvocableBy, +) +from .models import ( + AgentBlueprintUpdatedDataSessionSetting as AgentBlueprintUpdatedDataSessionSetting, +) +from .models import AgentInstanceCreated as AgentInstanceCreated +from .models import AgentInstanceCreatedData as AgentInstanceCreatedData +from .models import AgentInstanceDeleted as AgentInstanceDeleted +from .models import AgentInstanceDeletedData as AgentInstanceDeletedData +from .models import AgentInstanceSessionCreated as AgentInstanceSessionCreated +from .models import AgentInstanceSessionCreatedData as AgentInstanceSessionCreatedData +from .models import AgentInstanceSessionRevoked as AgentInstanceSessionRevoked +from .models import AgentInstanceSessionRevokedData as AgentInstanceSessionRevokedData +from .models import AgentInstanceSessionStatus as AgentInstanceSessionStatus +from .models import AgentInstanceType as AgentInstanceType from .models import ( AgentRegistrationClaimAttemptCreated as AgentRegistrationClaimAttemptCreated, ) @@ -224,6 +255,12 @@ from .models import ConnectionState as ConnectionState from .models import ConnectionStatus as ConnectionStatus from .models import ConnectionType as ConnectionType +from .models import ( + CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm as CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm, +) +from .models import ( + CreateConnectionOIDCOptionsTokenAuthenticationMethod as CreateConnectionOIDCOptionsTokenAuthenticationMethod, +) from .models import CreateDataIntegrationAuthMethods as CreateDataIntegrationAuthMethods from .models import CreateUserInviteOptionsLocale as CreateUserInviteOptionsLocale from .models import CreateUserPasswordHashType as CreateUserPasswordHashType @@ -373,6 +410,7 @@ from .models import InvitationRevoked as InvitationRevoked from .models import InvitationRevokedData as InvitationRevokedData from .models import InvitationState as InvitationState +from .models import InviteItContactIntents as InviteItContactIntents from .models import ListMetadata as ListMetadata from .models import MagicAuthCreated as MagicAuthCreated from .models import MagicAuthCreatedData as MagicAuthCreatedData @@ -432,6 +470,12 @@ from .models import PasswordResetCreatedData as PasswordResetCreatedData from .models import PasswordResetSucceeded as PasswordResetSucceeded from .models import PasswordResetSucceededData as PasswordResetSucceededData +from .models import ( + PatchConnectionOIDCOptionsIdTokenSignatureAlgorithm as PatchConnectionOIDCOptionsIdTokenSignatureAlgorithm, +) +from .models import ( + PatchConnectionOIDCOptionsTokenAuthenticationMethod as PatchConnectionOIDCOptionsTokenAuthenticationMethod, +) from .models import PermissionCreated as PermissionCreated from .models import PermissionCreatedData as PermissionCreatedData from .models import PermissionDeleted as PermissionDeleted @@ -467,6 +511,14 @@ from .models import RadarStandaloneResponseControl as RadarStandaloneResponseControl from .models import RadarStandaloneResponseVerdict as RadarStandaloneResponseVerdict from .models import ResendUserInviteOptionsLocale as ResendUserInviteOptionsLocale +from .models import ResourceExportCompleted as ResourceExportCompleted +from .models import ResourceExportCompletedData as ResourceExportCompletedData +from .models import ResourceExportCreated as ResourceExportCreated +from .models import ResourceExportCreatedData as ResourceExportCreatedData +from .models import ResourceExportDownloaded as ResourceExportDownloaded +from .models import ResourceExportDownloadedData as ResourceExportDownloadedData +from .models import ResourceExportFailed as ResourceExportFailed +from .models import ResourceExportFailedData as ResourceExportFailedData from .models import RoleCreated as RoleCreated from .models import RoleCreatedData as RoleCreatedData from .models import RoleDeleted as RoleDeleted @@ -481,6 +533,11 @@ from .models import SessionRevokedData as SessionRevokedData from .models import SessionRevokedDataImpersonator as SessionRevokedDataImpersonator from .models import SlimRole as SlimRole +from .models import TeamProductionState as TeamProductionState +from .models import TokenQueryGrantType as TokenQueryGrantType +from .models import ( + UpdateAuditLogsRetentionRetentionPeriod as UpdateAuditLogsRetentionRetentionPeriod, +) from .models import ( UpdateCustomProviderDefinitionAuthenticateVia as UpdateCustomProviderDefinitionAuthenticateVia, ) @@ -537,6 +594,7 @@ from .models import VaultMetadataReadData as VaultMetadataReadData from .models import VaultNamesListed as VaultNamesListed from .models import VaultNamesListedData as VaultNamesListedData +from .models import WaitlistEntryState as WaitlistEntryState from .models import WaitlistUser as WaitlistUser from .models import WaitlistUserApproved as WaitlistUserApproved from .models import WaitlistUserCreated as WaitlistUserCreated diff --git a/src/workos/common/models/__init__.py b/src/workos/common/models/__init__.py index ae7ca22e..9bc6f319 100644 --- a/src/workos/common/models/__init__.py +++ b/src/workos/common/models/__init__.py @@ -20,6 +20,57 @@ from .agent_admin_validate_credential_request_type import ( AgentAdminValidateCredentialRequestType as AgentAdminValidateCredentialRequestType, ) +from .agent_blueprint_created import AgentBlueprintCreated as AgentBlueprintCreated +from .agent_blueprint_created_data import ( + AgentBlueprintCreatedData as AgentBlueprintCreatedData, +) +from .agent_blueprint_created_data_invocable_by import ( + AgentBlueprintCreatedDataInvocableBy as AgentBlueprintCreatedDataInvocableBy, +) +from .agent_blueprint_created_data_session_setting import ( + AgentBlueprintCreatedDataSessionSetting as AgentBlueprintCreatedDataSessionSetting, +) +from .agent_blueprint_deleted import AgentBlueprintDeleted as AgentBlueprintDeleted +from .agent_blueprint_deleted_data import ( + AgentBlueprintDeletedData as AgentBlueprintDeletedData, +) +from .agent_blueprint_updated import AgentBlueprintUpdated as AgentBlueprintUpdated +from .agent_blueprint_updated_data import ( + AgentBlueprintUpdatedData as AgentBlueprintUpdatedData, +) +from .agent_blueprint_updated_data_invocable_by import ( + AgentBlueprintUpdatedDataInvocableBy as AgentBlueprintUpdatedDataInvocableBy, +) +from .agent_blueprint_updated_data_session_setting import ( + AgentBlueprintUpdatedDataSessionSetting as AgentBlueprintUpdatedDataSessionSetting, +) +from .agent_blueprints_token_mint_token_request_type import ( + AgentBlueprintsTokenMintTokenRequestType as AgentBlueprintsTokenMintTokenRequestType, +) +from .agent_instance_created import AgentInstanceCreated as AgentInstanceCreated +from .agent_instance_created_data import ( + AgentInstanceCreatedData as AgentInstanceCreatedData, +) +from .agent_instance_deleted import AgentInstanceDeleted as AgentInstanceDeleted +from .agent_instance_deleted_data import ( + AgentInstanceDeletedData as AgentInstanceDeletedData, +) +from .agent_instance_session_created import ( + AgentInstanceSessionCreated as AgentInstanceSessionCreated, +) +from .agent_instance_session_created_data import ( + AgentInstanceSessionCreatedData as AgentInstanceSessionCreatedData, +) +from .agent_instance_session_revoked import ( + AgentInstanceSessionRevoked as AgentInstanceSessionRevoked, +) +from .agent_instance_session_revoked_data import ( + AgentInstanceSessionRevokedData as AgentInstanceSessionRevokedData, +) +from .agent_instance_session_status import ( + AgentInstanceSessionStatus as AgentInstanceSessionStatus, +) +from .agent_instance_type import AgentInstanceType as AgentInstanceType from .agent_registration_claim_attempt_created import ( AgentRegistrationClaimAttemptCreated as AgentRegistrationClaimAttemptCreated, ) @@ -367,6 +418,12 @@ from .connection_state import ConnectionState as ConnectionState from .connection_status import ConnectionStatus as ConnectionStatus from .connection_type import ConnectionType as ConnectionType +from .create_connection_oidc_options_id_token_signature_algorithm import ( + CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm as CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm, +) +from .create_connection_oidc_options_token_authentication_method import ( + CreateConnectionOIDCOptionsTokenAuthenticationMethod as CreateConnectionOIDCOptionsTokenAuthenticationMethod, +) from .create_data_integration_auth_methods import ( CreateDataIntegrationAuthMethods as CreateDataIntegrationAuthMethods, ) @@ -571,6 +628,7 @@ from .invitation_revoked_data import InvitationRevokedData as InvitationRevokedData from .invitation_revoked_data_state import * from .invitation_state import InvitationState as InvitationState +from .invite_it_contact_intents import InviteItContactIntents as InviteItContactIntents from .list_metadata import ListMetadata as ListMetadata from .magic_auth_created import MagicAuthCreated as MagicAuthCreated from .magic_auth_created_data import MagicAuthCreatedData as MagicAuthCreatedData @@ -706,6 +764,12 @@ from .password_reset_succeeded_data import ( PasswordResetSucceededData as PasswordResetSucceededData, ) +from .patch_connection_oidc_options_id_token_signature_algorithm import ( + PatchConnectionOIDCOptionsIdTokenSignatureAlgorithm as PatchConnectionOIDCOptionsIdTokenSignatureAlgorithm, +) +from .patch_connection_oidc_options_token_authentication_method import ( + PatchConnectionOIDCOptionsTokenAuthenticationMethod as PatchConnectionOIDCOptionsTokenAuthenticationMethod, +) from .permission_created import PermissionCreated as PermissionCreated from .permission_created_data import PermissionCreatedData as PermissionCreatedData from .permission_deleted import PermissionDeleted as PermissionDeleted @@ -752,6 +816,26 @@ from .resend_user_invite_options_locale import ( ResendUserInviteOptionsLocale as ResendUserInviteOptionsLocale, ) +from .resource_export_completed import ( + ResourceExportCompleted as ResourceExportCompleted, +) +from .resource_export_completed_data import ( + ResourceExportCompletedData as ResourceExportCompletedData, +) +from .resource_export_created import ResourceExportCreated as ResourceExportCreated +from .resource_export_created_data import ( + ResourceExportCreatedData as ResourceExportCreatedData, +) +from .resource_export_downloaded import ( + ResourceExportDownloaded as ResourceExportDownloaded, +) +from .resource_export_downloaded_data import ( + ResourceExportDownloadedData as ResourceExportDownloadedData, +) +from .resource_export_failed import ResourceExportFailed as ResourceExportFailed +from .resource_export_failed_data import ( + ResourceExportFailedData as ResourceExportFailedData, +) from .role_created import RoleCreated as RoleCreated from .role_created_data import RoleCreatedData as RoleCreatedData from .role_deleted import RoleDeleted as RoleDeleted @@ -774,6 +858,11 @@ ) from .session_revoked_data_status import * from .slim_role import SlimRole as SlimRole +from .team_production_state import TeamProductionState as TeamProductionState +from .token_query_grant_type import TokenQueryGrantType as TokenQueryGrantType +from .update_audit_logs_retention_retention_period import ( + UpdateAuditLogsRetentionRetentionPeriod as UpdateAuditLogsRetentionRetentionPeriod, +) from .update_custom_provider_definition_authenticate_via import ( UpdateCustomProviderDefinitionAuthenticateVia as UpdateCustomProviderDefinitionAuthenticateVia, ) @@ -865,6 +954,7 @@ from .vault_names_listed import VaultNamesListed as VaultNamesListed from .vault_names_listed_data import VaultNamesListedData as VaultNamesListedData from .vault_names_listed_data_actor_source import * +from .waitlist_entry_state import WaitlistEntryState as WaitlistEntryState from .waitlist_user import WaitlistUser as WaitlistUser from .waitlist_user_approved import WaitlistUserApproved as WaitlistUserApproved from .waitlist_user_created import WaitlistUserCreated as WaitlistUserCreated diff --git a/src/workos/common/models/agent_blueprint_created.py b/src/workos/common/models/agent_blueprint_created.py new file mode 100644 index 00000000..72a73fe9 --- /dev/null +++ b/src/workos/common/models/agent_blueprint_created.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .agent_blueprint_created_data import AgentBlueprintCreatedData +from .event_context import EventContext + + +@dataclass(slots=True) +class AgentBlueprintCreated: + """Agent Blueprint Created model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["agent.blueprint.created"] + data: AgentBlueprintCreatedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintCreated: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "agent.blueprint.created"), + data=AgentBlueprintCreatedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintCreated", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/agent_blueprint_created_data.py b/src/workos/common/models/agent_blueprint_created_data.py new file mode 100644 index 00000000..987d068b --- /dev/null +++ b/src/workos/common/models/agent_blueprint_created_data.py @@ -0,0 +1,78 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal, cast + +from workos._types import _raise_deserialize_error + +from .agent_blueprint_created_data_invocable_by import ( + AgentBlueprintCreatedDataInvocableBy, +) +from .agent_blueprint_created_data_session_setting import ( + AgentBlueprintCreatedDataSessionSetting, +) + + +@dataclass(slots=True) +class AgentBlueprintCreatedData: + """The event payload.""" + + object: Literal["agent_blueprint"] + """Distinguishes the agent blueprint object.""" + id: str + """Unique identifier of the agent blueprint.""" + name: str + """Human-readable name of the agent blueprint.""" + description: str | None + """Human-readable description of the agent blueprint.""" + permissions: list[str] + """Permission slugs forming the ceiling on what sessions minted from this blueprint may do.""" + invocable_by: AgentBlueprintCreatedDataInvocableBy + """Who may mint sessions from this blueprint.""" + session_settings: AgentBlueprintCreatedDataSessionSetting + """Token and session lifetimes for sessions minted from this blueprint.""" + created_at: str + """The timestamp when the agent blueprint was created.""" + updated_at: str + """The timestamp when the agent blueprint was last updated.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintCreatedData: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "agent_blueprint"), + id=data["id"], + name=data["name"], + description=data["description"], + permissions=data["permissions"], + invocable_by=AgentBlueprintCreatedDataInvocableBy.from_dict( + cast(dict[str, Any], data["invocable_by"]) + ), + session_settings=AgentBlueprintCreatedDataSessionSetting.from_dict( + cast(dict[str, Any], data["session_settings"]) + ), + created_at=data["created_at"], + updated_at=data["updated_at"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintCreatedData", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["name"] = self.name + if self.description is not None: + result["description"] = self.description + else: + result["description"] = None + result["permissions"] = self.permissions + result["invocable_by"] = self.invocable_by.to_dict() + result["session_settings"] = self.session_settings.to_dict() + result["created_at"] = self.created_at + result["updated_at"] = self.updated_at + return result diff --git a/src/workos/common/models/agent_blueprint_created_data_invocable_by.py b/src/workos/common/models/agent_blueprint_created_data_invocable_by.py new file mode 100644 index 00000000..35df2986 --- /dev/null +++ b/src/workos/common/models/agent_blueprint_created_data_invocable_by.py @@ -0,0 +1,36 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AgentBlueprintCreatedDataInvocableBy: + """Who may mint sessions from this blueprint.""" + + role_slugs: list[str] + """Role slugs whose members may mint user-delegated sessions from this blueprint.""" + organization_ids: list[str] + """Organizations in which autonomous sessions may be minted from this blueprint.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintCreatedDataInvocableBy: + """Deserialize from a dictionary.""" + try: + return cls( + role_slugs=data["role_slugs"], + organization_ids=data["organization_ids"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintCreatedDataInvocableBy", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["role_slugs"] = self.role_slugs + result["organization_ids"] = self.organization_ids + return result diff --git a/src/workos/common/models/agent_blueprint_created_data_session_setting.py b/src/workos/common/models/agent_blueprint_created_data_session_setting.py new file mode 100644 index 00000000..b9a24dfb --- /dev/null +++ b/src/workos/common/models/agent_blueprint_created_data_session_setting.py @@ -0,0 +1,40 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AgentBlueprintCreatedDataSessionSetting: + """Token and session lifetimes for sessions minted from this blueprint.""" + + max_age_seconds: float + """Maximum lifetime of a session in seconds; refreshes never extend a session past this.""" + access_token_ttl_seconds: float + """Lifetime of each minted access token in seconds.""" + refresh_token_ttl_seconds: float + """Lifetime of each rotated refresh token in seconds.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintCreatedDataSessionSetting: + """Deserialize from a dictionary.""" + try: + return cls( + max_age_seconds=data["max_age_seconds"], + access_token_ttl_seconds=data["access_token_ttl_seconds"], + refresh_token_ttl_seconds=data["refresh_token_ttl_seconds"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintCreatedDataSessionSetting", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["max_age_seconds"] = self.max_age_seconds + result["access_token_ttl_seconds"] = self.access_token_ttl_seconds + result["refresh_token_ttl_seconds"] = self.refresh_token_ttl_seconds + return result diff --git a/src/workos/common/models/agent_blueprint_deleted.py b/src/workos/common/models/agent_blueprint_deleted.py new file mode 100644 index 00000000..943afc27 --- /dev/null +++ b/src/workos/common/models/agent_blueprint_deleted.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .agent_blueprint_deleted_data import AgentBlueprintDeletedData +from .event_context import EventContext + + +@dataclass(slots=True) +class AgentBlueprintDeleted: + """Agent Blueprint Deleted model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["agent.blueprint.deleted"] + data: AgentBlueprintDeletedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintDeleted: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "agent.blueprint.deleted"), + data=AgentBlueprintDeletedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintDeleted", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/agent_blueprint_deleted_data.py b/src/workos/common/models/agent_blueprint_deleted_data.py new file mode 100644 index 00000000..4cea907d --- /dev/null +++ b/src/workos/common/models/agent_blueprint_deleted_data.py @@ -0,0 +1,48 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AgentBlueprintDeletedData: + """The event payload.""" + + object: Literal["agent_blueprint"] + """Distinguishes the agent blueprint object.""" + id: str + """Unique identifier of the agent blueprint.""" + name: str + """Human-readable name of the agent blueprint.""" + created_at: str + """The timestamp when the agent blueprint was created.""" + updated_at: str + """The timestamp when the agent blueprint was last updated.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintDeletedData: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "agent_blueprint"), + id=data["id"], + name=data["name"], + created_at=data["created_at"], + updated_at=data["updated_at"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintDeletedData", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["name"] = self.name + result["created_at"] = self.created_at + result["updated_at"] = self.updated_at + return result diff --git a/src/workos/common/models/agent_blueprint_updated.py b/src/workos/common/models/agent_blueprint_updated.py new file mode 100644 index 00000000..0e378711 --- /dev/null +++ b/src/workos/common/models/agent_blueprint_updated.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .agent_blueprint_updated_data import AgentBlueprintUpdatedData +from .event_context import EventContext + + +@dataclass(slots=True) +class AgentBlueprintUpdated: + """Agent Blueprint Updated model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["agent.blueprint.updated"] + data: AgentBlueprintUpdatedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentBlueprintUpdated: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "agent.blueprint.updated"), + data=AgentBlueprintUpdatedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentBlueprintUpdated", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/agent_blueprint_updated_data.py b/src/workos/common/models/agent_blueprint_updated_data.py new file mode 100644 index 00000000..cebe3b19 --- /dev/null +++ b/src/workos/common/models/agent_blueprint_updated_data.py @@ -0,0 +1,7 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .agent_blueprint_created_data import AgentBlueprintCreatedData + +AgentBlueprintUpdatedData: TypeAlias = AgentBlueprintCreatedData diff --git a/src/workos/common/models/agent_blueprint_updated_data_invocable_by.py b/src/workos/common/models/agent_blueprint_updated_data_invocable_by.py new file mode 100644 index 00000000..212b3c25 --- /dev/null +++ b/src/workos/common/models/agent_blueprint_updated_data_invocable_by.py @@ -0,0 +1,9 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .agent_blueprint_created_data_invocable_by import ( + AgentBlueprintCreatedDataInvocableBy, +) + +AgentBlueprintUpdatedDataInvocableBy: TypeAlias = AgentBlueprintCreatedDataInvocableBy diff --git a/src/workos/common/models/agent_blueprint_updated_data_session_setting.py b/src/workos/common/models/agent_blueprint_updated_data_session_setting.py new file mode 100644 index 00000000..11fdf554 --- /dev/null +++ b/src/workos/common/models/agent_blueprint_updated_data_session_setting.py @@ -0,0 +1,11 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .agent_blueprint_created_data_session_setting import ( + AgentBlueprintCreatedDataSessionSetting, +) + +AgentBlueprintUpdatedDataSessionSetting: TypeAlias = ( + AgentBlueprintCreatedDataSessionSetting +) diff --git a/src/workos/common/models/agent_blueprints_token_mint_token_request_type.py b/src/workos/common/models/agent_blueprints_token_mint_token_request_type.py new file mode 100644 index 00000000..c22e5750 --- /dev/null +++ b/src/workos/common/models/agent_blueprints_token_mint_token_request_type.py @@ -0,0 +1,33 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of agent blueprints token mint token request type values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class AgentBlueprintsTokenMintTokenRequestType(str, Enum): + """Known values for AgentBlueprintsTokenMintTokenRequestType.""" + + USER_DELEGATED = "user_delegated" + AUTONOMOUS = "autonomous" + AGENT_DELEGATED = "agent_delegated" + REFRESH = "refresh" + + @classmethod + def _missing_( + cls, value: object + ) -> AgentBlueprintsTokenMintTokenRequestType | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +AgentBlueprintsTokenMintTokenRequestTypeLiteral: TypeAlias = Literal[ + "user_delegated", "autonomous", "agent_delegated", "refresh" +] diff --git a/src/workos/common/models/agent_instance_created.py b/src/workos/common/models/agent_instance_created.py new file mode 100644 index 00000000..9d69aabb --- /dev/null +++ b/src/workos/common/models/agent_instance_created.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .agent_instance_created_data import AgentInstanceCreatedData +from .event_context import EventContext + + +@dataclass(slots=True) +class AgentInstanceCreated: + """Agent Instance Created model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["agent.instance.created"] + data: AgentInstanceCreatedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentInstanceCreated: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "agent.instance.created"), + data=AgentInstanceCreatedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentInstanceCreated", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/agent_instance_created_data.py b/src/workos/common/models/agent_instance_created_data.py new file mode 100644 index 00000000..12eaecd4 --- /dev/null +++ b/src/workos/common/models/agent_instance_created_data.py @@ -0,0 +1,66 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any, Literal + +from workos._types import _raise_deserialize_error + +from .agent_instance_created_data_type import AgentInstanceCreatedDataType + + +@dataclass(slots=True) +class AgentInstanceCreatedData: + """The event payload.""" + + object: Literal["agent_instance"] + """Distinguishes the agent instance object.""" + id: str + """Unique identifier of the agent instance.""" + agent_blueprint_id: str + """The agent blueprint this instance was minted from.""" + organization_id: str + """The organization the instance acts within.""" + organization_membership_id: str | None + """The organization membership the instance acts on behalf of; `null` for an autonomous instance.""" + type: AgentInstanceCreatedDataType + """Whether the instance acts on behalf of a user (`delegated`) or as itself within an organization (`autonomous`).""" + created_at: str + """The timestamp when the agent instance was created.""" + updated_at: str + """The timestamp when the agent instance was last updated.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentInstanceCreatedData: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "agent_instance"), + id=data["id"], + agent_blueprint_id=data["agent_blueprint_id"], + organization_id=data["organization_id"], + organization_membership_id=data["organization_membership_id"], + type=AgentInstanceCreatedDataType(data["type"]), + created_at=data["created_at"], + updated_at=data["updated_at"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentInstanceCreatedData", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["agent_blueprint_id"] = self.agent_blueprint_id + result["organization_id"] = self.organization_id + if self.organization_membership_id is not None: + result["organization_membership_id"] = self.organization_membership_id + else: + result["organization_membership_id"] = None + result["type"] = self.type.value if isinstance(self.type, Enum) else self.type + result["created_at"] = self.created_at + result["updated_at"] = self.updated_at + return result diff --git a/src/workos/common/models/agent_instance_created_data_type.py b/src/workos/common/models/agent_instance_created_data_type.py new file mode 100644 index 00000000..c2f549d2 --- /dev/null +++ b/src/workos/common/models/agent_instance_created_data_type.py @@ -0,0 +1,27 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of agent instance created data type values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class AgentInstanceCreatedDataType(str, Enum): + """Known values for AgentInstanceCreatedDataType.""" + + DELEGATED = "delegated" + AUTONOMOUS = "autonomous" + + @classmethod + def _missing_(cls, value: object) -> AgentInstanceCreatedDataType | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +AgentInstanceCreatedDataTypeLiteral: TypeAlias = Literal["delegated", "autonomous"] diff --git a/src/workos/common/models/agent_instance_deleted.py b/src/workos/common/models/agent_instance_deleted.py new file mode 100644 index 00000000..415dbe95 --- /dev/null +++ b/src/workos/common/models/agent_instance_deleted.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .agent_instance_deleted_data import AgentInstanceDeletedData +from .event_context import EventContext + + +@dataclass(slots=True) +class AgentInstanceDeleted: + """Agent Instance Deleted model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["agent.instance.deleted"] + data: AgentInstanceDeletedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentInstanceDeleted: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "agent.instance.deleted"), + data=AgentInstanceDeletedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentInstanceDeleted", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/agent_instance_deleted_data.py b/src/workos/common/models/agent_instance_deleted_data.py new file mode 100644 index 00000000..7980a502 --- /dev/null +++ b/src/workos/common/models/agent_instance_deleted_data.py @@ -0,0 +1,7 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .agent_instance_created_data import AgentInstanceCreatedData + +AgentInstanceDeletedData: TypeAlias = AgentInstanceCreatedData diff --git a/src/workos/common/models/agent_instance_deleted_data_type.py b/src/workos/common/models/agent_instance_deleted_data_type.py new file mode 100644 index 00000000..77860aef --- /dev/null +++ b/src/workos/common/models/agent_instance_deleted_data_type.py @@ -0,0 +1,8 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .agent_instance_created_data_type import AgentInstanceCreatedDataType + +AgentInstanceDeletedDataType: TypeAlias = AgentInstanceCreatedDataType +__all__ = ["AgentInstanceDeletedDataType"] diff --git a/src/workos/common/models/agent_instance_session_created.py b/src/workos/common/models/agent_instance_session_created.py new file mode 100644 index 00000000..d5139626 --- /dev/null +++ b/src/workos/common/models/agent_instance_session_created.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .agent_instance_session_created_data import AgentInstanceSessionCreatedData +from .event_context import EventContext + + +@dataclass(slots=True) +class AgentInstanceSessionCreated: + """Agent Instance Session Created model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["agent.instance.session.created"] + data: AgentInstanceSessionCreatedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentInstanceSessionCreated: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "agent.instance.session.created"), + data=AgentInstanceSessionCreatedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentInstanceSessionCreated", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/agent_instance_session_created_data.py b/src/workos/common/models/agent_instance_session_created_data.py new file mode 100644 index 00000000..c66f288b --- /dev/null +++ b/src/workos/common/models/agent_instance_session_created_data.py @@ -0,0 +1,67 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AgentInstanceSessionCreatedData: + """The event payload.""" + + object: Literal["agent_instance_session"] + """Distinguishes the agent instance session object.""" + id: str + """Unique identifier of the agent instance session.""" + agent_instance_id: str + """The agent instance the session belongs to.""" + organization_id: str + """The organization the owning agent instance belongs to.""" + expires_at: str + """Timestamp when the session expires.""" + revoked_at: str | None + """Timestamp when the session was revoked; `null` if it has not been revoked.""" + created_at: str + """The timestamp when the session was created.""" + updated_at: str + """The timestamp when the session was last updated.""" + permission_slugs: list[str] + """The permissions granted to the session at mint time.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentInstanceSessionCreatedData: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "agent_instance_session"), + id=data["id"], + agent_instance_id=data["agent_instance_id"], + organization_id=data["organization_id"], + expires_at=data["expires_at"], + revoked_at=data["revoked_at"], + created_at=data["created_at"], + updated_at=data["updated_at"], + permission_slugs=data["permission_slugs"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentInstanceSessionCreatedData", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["agent_instance_id"] = self.agent_instance_id + result["organization_id"] = self.organization_id + result["expires_at"] = self.expires_at + if self.revoked_at is not None: + result["revoked_at"] = self.revoked_at + else: + result["revoked_at"] = None + result["created_at"] = self.created_at + result["updated_at"] = self.updated_at + result["permission_slugs"] = self.permission_slugs + return result diff --git a/src/workos/common/models/agent_instance_session_revoked.py b/src/workos/common/models/agent_instance_session_revoked.py new file mode 100644 index 00000000..1f86fd52 --- /dev/null +++ b/src/workos/common/models/agent_instance_session_revoked.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .agent_instance_session_revoked_data import AgentInstanceSessionRevokedData +from .event_context import EventContext + + +@dataclass(slots=True) +class AgentInstanceSessionRevoked: + """Agent Instance Session Revoked model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["agent.instance.session.revoked"] + data: AgentInstanceSessionRevokedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentInstanceSessionRevoked: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "agent.instance.session.revoked"), + data=AgentInstanceSessionRevokedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentInstanceSessionRevoked", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/agent_instance_session_revoked_data.py b/src/workos/common/models/agent_instance_session_revoked_data.py new file mode 100644 index 00000000..7c196272 --- /dev/null +++ b/src/workos/common/models/agent_instance_session_revoked_data.py @@ -0,0 +1,63 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AgentInstanceSessionRevokedData: + """The event payload.""" + + object: Literal["agent_instance_session"] + """Distinguishes the agent instance session object.""" + id: str + """Unique identifier of the agent instance session.""" + agent_instance_id: str + """The agent instance the session belongs to.""" + organization_id: str + """The organization the owning agent instance belongs to.""" + expires_at: str + """Timestamp when the session expires.""" + revoked_at: str | None + """Timestamp when the session was revoked; `null` if it has not been revoked.""" + created_at: str + """The timestamp when the session was created.""" + updated_at: str + """The timestamp when the session was last updated.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AgentInstanceSessionRevokedData: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "agent_instance_session"), + id=data["id"], + agent_instance_id=data["agent_instance_id"], + organization_id=data["organization_id"], + expires_at=data["expires_at"], + revoked_at=data["revoked_at"], + created_at=data["created_at"], + updated_at=data["updated_at"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AgentInstanceSessionRevokedData", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["agent_instance_id"] = self.agent_instance_id + result["organization_id"] = self.organization_id + result["expires_at"] = self.expires_at + if self.revoked_at is not None: + result["revoked_at"] = self.revoked_at + else: + result["revoked_at"] = None + result["created_at"] = self.created_at + result["updated_at"] = self.updated_at + return result diff --git a/src/workos/common/models/agent_instance_session_status.py b/src/workos/common/models/agent_instance_session_status.py new file mode 100644 index 00000000..7b7c44f5 --- /dev/null +++ b/src/workos/common/models/agent_instance_session_status.py @@ -0,0 +1,28 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of agent instance session status values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class AgentInstanceSessionStatus(str, Enum): + """Known values for AgentInstanceSessionStatus.""" + + ACTIVE = "active" + REVOKED = "revoked" + EXPIRED = "expired" + + @classmethod + def _missing_(cls, value: object) -> AgentInstanceSessionStatus | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +AgentInstanceSessionStatusLiteral: TypeAlias = Literal["active", "revoked", "expired"] diff --git a/src/workos/common/models/agent_instance_type.py b/src/workos/common/models/agent_instance_type.py new file mode 100644 index 00000000..ffb91179 --- /dev/null +++ b/src/workos/common/models/agent_instance_type.py @@ -0,0 +1,8 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .agent_instance_created_data_type import AgentInstanceCreatedDataType + +AgentInstanceType: TypeAlias = AgentInstanceCreatedDataType +__all__ = ["AgentInstanceType"] diff --git a/src/workos/common/models/authentication_oauth_failed_data.py b/src/workos/common/models/authentication_oauth_failed_data.py index 68023b49..3645265b 100644 --- a/src/workos/common/models/authentication_oauth_failed_data.py +++ b/src/workos/common/models/authentication_oauth_failed_data.py @@ -26,6 +26,8 @@ class AuthenticationOAuthFailedData: """The email address of the user.""" error: AuthenticationOAuthFailedDataError """Details about the authentication error.""" + provider: str | None = None + """The OAuth provider used for authentication.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> AuthenticationOAuthFailedData: @@ -41,6 +43,7 @@ def from_dict(cls, data: dict[str, Any]) -> AuthenticationOAuthFailedData: error=AuthenticationOAuthFailedDataError.from_dict( cast(dict[str, Any], data["error"]) ), + provider=data.get("provider"), ) except (KeyError, ValueError) as e: _raise_deserialize_error("AuthenticationOAuthFailedData", e) @@ -67,4 +70,6 @@ def to_dict(self) -> dict[str, Any]: else: result["email"] = None result["error"] = self.error.to_dict() + if self.provider is not None: + result["provider"] = self.provider return result diff --git a/src/workos/common/models/authentication_oauth_succeeded_data.py b/src/workos/common/models/authentication_oauth_succeeded_data.py index 9aa48dfd..9d2f8a1a 100644 --- a/src/workos/common/models/authentication_oauth_succeeded_data.py +++ b/src/workos/common/models/authentication_oauth_succeeded_data.py @@ -22,6 +22,8 @@ class AuthenticationOAuthSucceededData: """The ID of the user.""" email: str """The email address of the user.""" + provider: str | None = None + """The OAuth provider used for authentication.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> AuthenticationOAuthSucceededData: @@ -34,6 +36,7 @@ def from_dict(cls, data: dict[str, Any]) -> AuthenticationOAuthSucceededData: user_agent=data["user_agent"], user_id=data["user_id"], email=data["email"], + provider=data.get("provider"), ) except (KeyError, ValueError) as e: _raise_deserialize_error("AuthenticationOAuthSucceededData", e) @@ -56,4 +59,6 @@ def to_dict(self) -> dict[str, Any]: else: result["user_id"] = None result["email"] = self.email + if self.provider is not None: + result["provider"] = self.provider return result diff --git a/src/workos/common/models/connection_activated_data_connection_type.py b/src/workos/common/models/connection_activated_data_connection_type.py index 861d23f7..7a52b073 100644 --- a/src/workos/common/models/connection_activated_data_connection_type.py +++ b/src/workos/common/models/connection_activated_data_connection_type.py @@ -23,7 +23,6 @@ class ConnectionActivatedDataConnectionType(str, Enum): CLEVER_OIDC = "CleverOIDC" CLOUDFLARE_SAML = "CloudflareSAML" CYBER_ARK_SAML = "CyberArkSAML" - DISCORD_OAUTH = "DiscordOAuth" DUO_SAML = "DuoSAML" ENTRA_ID_OIDC = "EntraIdOIDC" GENERIC_OIDC = "GenericOIDC" @@ -33,8 +32,6 @@ class ConnectionActivatedDataConnectionType(str, Enum): GOOGLE_OAUTH = "GoogleOAuth" GOOGLE_OIDC = "GoogleOIDC" GOOGLE_SAML = "GoogleSAML" - GROK_OAUTH = "GrokOAuth" - XO_AUTH = "XOAuth" INTUIT_OAUTH = "IntuitOAuth" JUMP_CLOUD_SAML = "JumpCloudSAML" KEYCLOAK_SAML = "KeycloakSAML" @@ -87,7 +84,6 @@ def _missing_(cls, value: object) -> ConnectionActivatedDataConnectionType | Non "CleverOIDC", "CloudflareSAML", "CyberArkSAML", - "DiscordOAuth", "DuoSAML", "EntraIdOIDC", "GenericOIDC", @@ -97,8 +93,6 @@ def _missing_(cls, value: object) -> ConnectionActivatedDataConnectionType | Non "GoogleOAuth", "GoogleOIDC", "GoogleSAML", - "GrokOAuth", - "XOAuth", "IntuitOAuth", "JumpCloudSAML", "KeycloakSAML", diff --git a/src/workos/common/models/connection_type.py b/src/workos/common/models/connection_type.py index 683aa706..40229ee1 100644 --- a/src/workos/common/models/connection_type.py +++ b/src/workos/common/models/connection_type.py @@ -24,7 +24,6 @@ class ConnectionType(str, Enum): CLEVER_OIDC = "CleverOIDC" CLOUDFLARE_SAML = "CloudflareSAML" CYBER_ARK_SAML = "CyberArkSAML" - DISCORD_OAUTH = "DiscordOAuth" DUO_SAML = "DuoSAML" ENTRA_ID_OIDC = "EntraIdOIDC" GENERIC_OIDC = "GenericOIDC" @@ -34,8 +33,6 @@ class ConnectionType(str, Enum): GOOGLE_OAUTH = "GoogleOAuth" GOOGLE_OIDC = "GoogleOIDC" GOOGLE_SAML = "GoogleSAML" - GROK_OAUTH = "GrokOAuth" - XO_AUTH = "XOAuth" INTUIT_OAUTH = "IntuitOAuth" JUMP_CLOUD_SAML = "JumpCloudSAML" KEYCLOAK_SAML = "KeycloakSAML" @@ -89,7 +86,6 @@ def _missing_(cls, value: object) -> ConnectionType | None: "CleverOIDC", "CloudflareSAML", "CyberArkSAML", - "DiscordOAuth", "DuoSAML", "EntraIdOIDC", "GenericOIDC", @@ -99,8 +95,6 @@ def _missing_(cls, value: object) -> ConnectionType | None: "GoogleOAuth", "GoogleOIDC", "GoogleSAML", - "GrokOAuth", - "XOAuth", "IntuitOAuth", "JumpCloudSAML", "KeycloakSAML", diff --git a/src/workos/common/models/create_connection_oidc_options_id_token_signature_algorithm.py b/src/workos/common/models/create_connection_oidc_options_id_token_signature_algorithm.py new file mode 100644 index 00000000..edecc78c --- /dev/null +++ b/src/workos/common/models/create_connection_oidc_options_id_token_signature_algorithm.py @@ -0,0 +1,54 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of create connection oidc options id token signature algorithm values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm(str, Enum): + """Known values for CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm.""" + + ES_256 = "ES256" + ES_384 = "ES384" + ES_512 = "ES512" + ED_DSA = "EdDSA" + HS_256 = "HS256" + HS_384 = "HS384" + HS_512 = "HS512" + PS_256 = "PS256" + PS_384 = "PS384" + PS_512 = "PS512" + RS_256 = "RS256" + RS_384 = "RS384" + RS_512 = "RS512" + + @classmethod + def _missing_( + cls, value: object + ) -> CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +CreateConnectionOIDCOptionsIdTokenSignatureAlgorithmLiteral: TypeAlias = Literal[ + "ES256", + "ES384", + "ES512", + "EdDSA", + "HS256", + "HS384", + "HS512", + "PS256", + "PS384", + "PS512", + "RS256", + "RS384", + "RS512", +] diff --git a/src/workos/common/models/create_connection_oidc_options_token_authentication_method.py b/src/workos/common/models/create_connection_oidc_options_token_authentication_method.py new file mode 100644 index 00000000..324a4b93 --- /dev/null +++ b/src/workos/common/models/create_connection_oidc_options_token_authentication_method.py @@ -0,0 +1,32 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of create connection oidc options token authentication method values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class CreateConnectionOIDCOptionsTokenAuthenticationMethod(str, Enum): + """Known values for CreateConnectionOIDCOptionsTokenAuthenticationMethod.""" + + CLIENT_SECRET_POST = "client_secret_post" + CLIENT_SECRET_BASIC = "client_secret_basic" + PRIVATE_KEY_JWT = "private_key_jwt" + + @classmethod + def _missing_( + cls, value: object + ) -> CreateConnectionOIDCOptionsTokenAuthenticationMethod | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +CreateConnectionOIDCOptionsTokenAuthenticationMethodLiteral: TypeAlias = Literal[ + "client_secret_post", "client_secret_basic", "private_key_jwt" +] diff --git a/src/workos/common/models/create_webhook_endpoint_events.py b/src/workos/common/models/create_webhook_endpoint_events.py index 99d60301..987fbc4e 100644 --- a/src/workos/common/models/create_webhook_endpoint_events.py +++ b/src/workos/common/models/create_webhook_endpoint_events.py @@ -11,6 +11,13 @@ class CreateWebhookEndpointEvents(str, Enum): """Known values for CreateWebhookEndpointEvents.""" + AGENT_BLUEPRINT_CREATED = "agent.blueprint.created" + AGENT_BLUEPRINT_DELETED = "agent.blueprint.deleted" + AGENT_BLUEPRINT_UPDATED = "agent.blueprint.updated" + AGENT_INSTANCE_CREATED = "agent.instance.created" + AGENT_INSTANCE_DELETED = "agent.instance.deleted" + AGENT_INSTANCE_SESSION_CREATED = "agent.instance.session.created" + AGENT_INSTANCE_SESSION_REVOKED = "agent.instance.session.revoked" AGENT_REGISTRATION_CREATED = "agent.registration.created" AGENT_REGISTRATION_CLAIM_ATTEMPT_CREATED = ( "agent.registration.claim.attempt.created" @@ -130,6 +137,13 @@ def _missing_(cls, value: object) -> CreateWebhookEndpointEvents | None: CreateWebhookEndpointEventsLiteral: TypeAlias = Literal[ + "agent.blueprint.created", + "agent.blueprint.deleted", + "agent.blueprint.updated", + "agent.instance.created", + "agent.instance.deleted", + "agent.instance.session.created", + "agent.instance.session.revoked", "agent.registration.created", "agent.registration.claim.attempt.created", "agent.registration.claim.completed", diff --git a/src/workos/common/models/invite_it_contact_intents.py b/src/workos/common/models/invite_it_contact_intents.py new file mode 100644 index 00000000..2ab1da0e --- /dev/null +++ b/src/workos/common/models/invite_it_contact_intents.py @@ -0,0 +1,32 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of invite it contact intents values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class InviteItContactIntents(str, Enum): + """Known values for InviteItContactIntents.""" + + SSO = "sso" + DIRECTORY_SYNC = "directory_sync" + LOG_STREAMS = "log_streams" + DOMAIN_VERIFICATION = "domain_verification" + BRING_YOUR_OWN_KEY = "bring_your_own_key" + + @classmethod + def _missing_(cls, value: object) -> InviteItContactIntents | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +InviteItContactIntentsLiteral: TypeAlias = Literal[ + "sso", "directory_sync", "log_streams", "domain_verification", "bring_your_own_key" +] diff --git a/src/workos/common/models/patch_connection_oidc_options_id_token_signature_algorithm.py b/src/workos/common/models/patch_connection_oidc_options_id_token_signature_algorithm.py new file mode 100644 index 00000000..72b10052 --- /dev/null +++ b/src/workos/common/models/patch_connection_oidc_options_id_token_signature_algorithm.py @@ -0,0 +1,12 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .create_connection_oidc_options_id_token_signature_algorithm import ( + CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm, +) + +PatchConnectionOIDCOptionsIdTokenSignatureAlgorithm: TypeAlias = ( + CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm +) +__all__ = ["PatchConnectionOIDCOptionsIdTokenSignatureAlgorithm"] diff --git a/src/workos/common/models/patch_connection_oidc_options_token_authentication_method.py b/src/workos/common/models/patch_connection_oidc_options_token_authentication_method.py new file mode 100644 index 00000000..e44cdd45 --- /dev/null +++ b/src/workos/common/models/patch_connection_oidc_options_token_authentication_method.py @@ -0,0 +1,12 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .create_connection_oidc_options_token_authentication_method import ( + CreateConnectionOIDCOptionsTokenAuthenticationMethod, +) + +PatchConnectionOIDCOptionsTokenAuthenticationMethod: TypeAlias = ( + CreateConnectionOIDCOptionsTokenAuthenticationMethod +) +__all__ = ["PatchConnectionOIDCOptionsTokenAuthenticationMethod"] diff --git a/src/workos/common/models/resource_export_completed.py b/src/workos/common/models/resource_export_completed.py new file mode 100644 index 00000000..b9691d5b --- /dev/null +++ b/src/workos/common/models/resource_export_completed.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .event_context import EventContext +from .resource_export_completed_data import ResourceExportCompletedData + + +@dataclass(slots=True) +class ResourceExportCompleted: + """Resource Export Completed model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["resource_export.completed"] + data: ResourceExportCompletedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ResourceExportCompleted: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "resource_export.completed"), + data=ResourceExportCompletedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ResourceExportCompleted", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/resource_export_completed_data.py b/src/workos/common/models/resource_export_completed_data.py new file mode 100644 index 00000000..b2ec5c3c --- /dev/null +++ b/src/workos/common/models/resource_export_completed_data.py @@ -0,0 +1,47 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any + +from workos._types import _raise_deserialize_error + +from .resource_export_completed_data_resource_type import ( + ResourceExportCompletedDataResourceType, +) + + +@dataclass(slots=True) +class ResourceExportCompletedData: + """The event payload.""" + + id: str + """The ID of the resource export.""" + resource_type: ResourceExportCompletedDataResourceType + """The type of resource being exported.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ResourceExportCompletedData: + """Deserialize from a dictionary.""" + try: + return cls( + id=data["id"], + resource_type=ResourceExportCompletedDataResourceType( + data["resource_type"] + ), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ResourceExportCompletedData", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["id"] = self.id + result["resource_type"] = ( + self.resource_type.value + if isinstance(self.resource_type, Enum) + else self.resource_type + ) + return result diff --git a/src/workos/common/models/resource_export_completed_data_resource_type.py b/src/workos/common/models/resource_export_completed_data_resource_type.py new file mode 100644 index 00000000..47f02db0 --- /dev/null +++ b/src/workos/common/models/resource_export_completed_data_resource_type.py @@ -0,0 +1,32 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of resource export completed data resource type values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class ResourceExportCompletedDataResourceType(str, Enum): + """Known values for ResourceExportCompletedDataResourceType.""" + + USERS = "users" + ORGANIZATIONS = "organizations" + EVENTS = "events" + SESSIONS = "sessions" + AUDIT_LOG_EVENTS = "auditLogEvents" + + @classmethod + def _missing_(cls, value: object) -> ResourceExportCompletedDataResourceType | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +ResourceExportCompletedDataResourceTypeLiteral: TypeAlias = Literal[ + "users", "organizations", "events", "sessions", "auditLogEvents" +] diff --git a/src/workos/common/models/resource_export_created.py b/src/workos/common/models/resource_export_created.py new file mode 100644 index 00000000..7deb8aea --- /dev/null +++ b/src/workos/common/models/resource_export_created.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .event_context import EventContext +from .resource_export_created_data import ResourceExportCreatedData + + +@dataclass(slots=True) +class ResourceExportCreated: + """Resource Export Created model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["resource_export.created"] + data: ResourceExportCreatedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ResourceExportCreated: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "resource_export.created"), + data=ResourceExportCreatedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ResourceExportCreated", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/resource_export_created_data.py b/src/workos/common/models/resource_export_created_data.py new file mode 100644 index 00000000..d2b68ab9 --- /dev/null +++ b/src/workos/common/models/resource_export_created_data.py @@ -0,0 +1,7 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .resource_export_completed_data import ResourceExportCompletedData + +ResourceExportCreatedData: TypeAlias = ResourceExportCompletedData diff --git a/src/workos/common/models/resource_export_created_data_resource_type.py b/src/workos/common/models/resource_export_created_data_resource_type.py new file mode 100644 index 00000000..1df388a5 --- /dev/null +++ b/src/workos/common/models/resource_export_created_data_resource_type.py @@ -0,0 +1,12 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .resource_export_completed_data_resource_type import ( + ResourceExportCompletedDataResourceType, +) + +ResourceExportCreatedDataResourceType: TypeAlias = ( + ResourceExportCompletedDataResourceType +) +__all__ = ["ResourceExportCreatedDataResourceType"] diff --git a/src/workos/common/models/resource_export_downloaded.py b/src/workos/common/models/resource_export_downloaded.py new file mode 100644 index 00000000..948d2c92 --- /dev/null +++ b/src/workos/common/models/resource_export_downloaded.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .event_context import EventContext +from .resource_export_downloaded_data import ResourceExportDownloadedData + + +@dataclass(slots=True) +class ResourceExportDownloaded: + """Resource Export Downloaded model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["resource_export.downloaded"] + data: ResourceExportDownloadedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ResourceExportDownloaded: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "resource_export.downloaded"), + data=ResourceExportDownloadedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ResourceExportDownloaded", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/resource_export_downloaded_data.py b/src/workos/common/models/resource_export_downloaded_data.py new file mode 100644 index 00000000..1e2be152 --- /dev/null +++ b/src/workos/common/models/resource_export_downloaded_data.py @@ -0,0 +1,7 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .resource_export_completed_data import ResourceExportCompletedData + +ResourceExportDownloadedData: TypeAlias = ResourceExportCompletedData diff --git a/src/workos/common/models/resource_export_downloaded_data_resource_type.py b/src/workos/common/models/resource_export_downloaded_data_resource_type.py new file mode 100644 index 00000000..cb7a78a0 --- /dev/null +++ b/src/workos/common/models/resource_export_downloaded_data_resource_type.py @@ -0,0 +1,12 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .resource_export_completed_data_resource_type import ( + ResourceExportCompletedDataResourceType, +) + +ResourceExportDownloadedDataResourceType: TypeAlias = ( + ResourceExportCompletedDataResourceType +) +__all__ = ["ResourceExportDownloadedDataResourceType"] diff --git a/src/workos/common/models/resource_export_failed.py b/src/workos/common/models/resource_export_failed.py new file mode 100644 index 00000000..1621e899 --- /dev/null +++ b/src/workos/common/models/resource_export_failed.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .event_context import EventContext +from .resource_export_failed_data import ResourceExportFailedData + + +@dataclass(slots=True) +class ResourceExportFailed: + """Resource Export Failed model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["resource_export.failed"] + data: ResourceExportFailedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ResourceExportFailed: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "resource_export.failed"), + data=ResourceExportFailedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ResourceExportFailed", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/resource_export_failed_data.py b/src/workos/common/models/resource_export_failed_data.py new file mode 100644 index 00000000..59ab3555 --- /dev/null +++ b/src/workos/common/models/resource_export_failed_data.py @@ -0,0 +1,7 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .resource_export_completed_data import ResourceExportCompletedData + +ResourceExportFailedData: TypeAlias = ResourceExportCompletedData diff --git a/src/workos/common/models/resource_export_failed_data_resource_type.py b/src/workos/common/models/resource_export_failed_data_resource_type.py new file mode 100644 index 00000000..b20b5396 --- /dev/null +++ b/src/workos/common/models/resource_export_failed_data_resource_type.py @@ -0,0 +1,12 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .resource_export_completed_data_resource_type import ( + ResourceExportCompletedDataResourceType, +) + +ResourceExportFailedDataResourceType: TypeAlias = ( + ResourceExportCompletedDataResourceType +) +__all__ = ["ResourceExportFailedDataResourceType"] diff --git a/src/workos/common/models/session_created_data_status.py b/src/workos/common/models/session_created_data_status.py index 63fcace7..cb721b4f 100644 --- a/src/workos/common/models/session_created_data_status.py +++ b/src/workos/common/models/session_created_data_status.py @@ -1,28 +1,8 @@ # This file is auto-generated by oagen. Do not edit. -"""Enumeration of session created data status values.""" +from typing import TypeAlias -from __future__ import annotations +from .agent_instance_session_status import AgentInstanceSessionStatus -from enum import Enum -from typing import Literal, TypeAlias - - -class SessionCreatedDataStatus(str, Enum): - """Known values for SessionCreatedDataStatus.""" - - ACTIVE = "active" - EXPIRED = "expired" - REVOKED = "revoked" - - @classmethod - def _missing_(cls, value: object) -> SessionCreatedDataStatus | None: - if not isinstance(value, str): - return None - unknown = str.__new__(cls, value) - unknown._name_ = value.upper() - unknown._value_ = value - return unknown - - -SessionCreatedDataStatusLiteral: TypeAlias = Literal["active", "expired", "revoked"] +SessionCreatedDataStatus: TypeAlias = AgentInstanceSessionStatus +__all__ = ["SessionCreatedDataStatus"] diff --git a/src/workos/common/models/session_revoked_data_status.py b/src/workos/common/models/session_revoked_data_status.py index 796e1971..a86e77b6 100644 --- a/src/workos/common/models/session_revoked_data_status.py +++ b/src/workos/common/models/session_revoked_data_status.py @@ -1,7 +1,8 @@ # This file is auto-generated by oagen. Do not edit. from typing import TypeAlias -from .session_created_data_status import SessionCreatedDataStatus -SessionRevokedDataStatus: TypeAlias = SessionCreatedDataStatus +from .agent_instance_session_status import AgentInstanceSessionStatus + +SessionRevokedDataStatus: TypeAlias = AgentInstanceSessionStatus __all__ = ["SessionRevokedDataStatus"] diff --git a/src/workos/common/models/team_production_state.py b/src/workos/common/models/team_production_state.py new file mode 100644 index 00000000..41bb88bf --- /dev/null +++ b/src/workos/common/models/team_production_state.py @@ -0,0 +1,31 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of team production state values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class TeamProductionState(str, Enum): + """Known values for TeamProductionState.""" + + ACTIVE = "Active" + INACTIVE = "Inactive" + SUSPENDED = "Suspended" + DELETING = "Deleting" + + @classmethod + def _missing_(cls, value: object) -> TeamProductionState | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +TeamProductionStateLiteral: TypeAlias = Literal[ + "Active", "Inactive", "Suspended", "Deleting" +] diff --git a/src/workos/common/models/token_query_grant_type.py b/src/workos/common/models/token_query_grant_type.py new file mode 100644 index 00000000..9c7d2e31 --- /dev/null +++ b/src/workos/common/models/token_query_grant_type.py @@ -0,0 +1,17 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from workos.sso.models.sso_grant_type import SSOGrantType as TokenQueryGrantType +else: + + def __getattr__(name: str): + if name == "TokenQueryGrantType": + from workos.sso.models.sso_grant_type import SSOGrantType + + return SSOGrantType + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +__all__ = ["TokenQueryGrantType"] diff --git a/src/workos/common/models/user_identities_get_item_provider.py b/src/workos/common/models/user_identities_get_item_provider.py index 1e512d8f..373a49aa 100644 --- a/src/workos/common/models/user_identities_get_item_provider.py +++ b/src/workos/common/models/user_identities_get_item_provider.py @@ -13,12 +13,9 @@ class UserIdentitiesGetItemProvider(str, Enum): APPLE_OAUTH = "AppleOAuth" BITBUCKET_OAUTH = "BitbucketOAuth" - DISCORD_OAUTH = "DiscordOAuth" GITHUB_OAUTH = "GithubOAuth" GIT_LAB_OAUTH = "GitLabOAuth" GOOGLE_OAUTH = "GoogleOAuth" - GROK_OAUTH = "GrokOAuth" - XO_AUTH = "XOAuth" INTUIT_OAUTH = "IntuitOAuth" LINKED_IN_OAUTH = "LinkedInOAuth" MICROSOFT_OAUTH = "MicrosoftOAuth" @@ -41,12 +38,9 @@ def _missing_(cls, value: object) -> UserIdentitiesGetItemProvider | None: UserIdentitiesGetItemProviderLiteral: TypeAlias = Literal[ "AppleOAuth", "BitbucketOAuth", - "DiscordOAuth", "GithubOAuth", "GitLabOAuth", "GoogleOAuth", - "GrokOAuth", - "XOAuth", "IntuitOAuth", "LinkedInOAuth", "MicrosoftOAuth", diff --git a/src/workos/common/models/user_sessions_status.py b/src/workos/common/models/user_sessions_status.py index 3c075ba3..4ae023f9 100644 --- a/src/workos/common/models/user_sessions_status.py +++ b/src/workos/common/models/user_sessions_status.py @@ -1,7 +1,8 @@ # This file is auto-generated by oagen. Do not edit. from typing import TypeAlias -from .session_created_data_status import SessionCreatedDataStatus -UserSessionsStatus: TypeAlias = SessionCreatedDataStatus +from .agent_instance_session_status import AgentInstanceSessionStatus + +UserSessionsStatus: TypeAlias = AgentInstanceSessionStatus __all__ = ["UserSessionsStatus"] diff --git a/src/workos/common/models/waitlist_entry_state.py b/src/workos/common/models/waitlist_entry_state.py new file mode 100644 index 00000000..f71ae751 --- /dev/null +++ b/src/workos/common/models/waitlist_entry_state.py @@ -0,0 +1,21 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from workos.user_management.models.user_management_waitlists_state import ( + UserManagementWaitlistsState as WaitlistEntryState, + ) +else: + + def __getattr__(name: str): + if name == "WaitlistEntryState": + from workos.user_management.models.user_management_waitlists_state import ( + UserManagementWaitlistsState, + ) + + return UserManagementWaitlistsState + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +__all__ = ["WaitlistEntryState"] diff --git a/src/workos/common/models/waitlist_user.py b/src/workos/common/models/waitlist_user.py index 2135eafe..da0689a7 100644 --- a/src/workos/common/models/waitlist_user.py +++ b/src/workos/common/models/waitlist_user.py @@ -16,29 +16,30 @@ class WaitlistUser: """Waitlist User model.""" - object: Literal["waitlist_user"] - """Distinguishes the Waitlist User object.""" id: str - """The unique ID of the Waitlist User.""" + """The unique ID of the waitlist entry.""" email: str - """The email address of the Waitlist User.""" + """The email address of the user on the waitlist.""" state: WaitlistUserState - """The state of the Waitlist User.""" + """The state of the waitlist entry.""" approved_at: datetime | None - """The timestamp when the Waitlist User was approved, or null if not yet approved.""" + """The timestamp when the entry was approved, or null if not yet approved.""" created_at: datetime """An ISO 8601 timestamp.""" updated_at: datetime """An ISO 8601 timestamp.""" + object: Literal["waitlist_user"] + """Distinguishes the Waitlist User object.""" + additional_fields: dict[str, str] | None = None + """Additional fields submitted when the user joined the waitlist. Values are user-provided — treat them as untrusted input when rendering or exporting.""" waitlist_id: str | None = None - """The unique ID of the Waitlist that the Waitlist User joined.""" + """The unique ID of the waitlist the entry belongs to.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> WaitlistUser: """Deserialize from a dictionary.""" try: return cls( - object=data.get("object", "waitlist_user"), id=data["id"], email=data["email"], state=WaitlistUserState(data["state"]), @@ -47,6 +48,8 @@ def from_dict(cls, data: dict[str, Any]) -> WaitlistUser: else None, created_at=_parse_datetime(data["created_at"]), updated_at=_parse_datetime(data["updated_at"]), + object=data.get("object", "waitlist_user"), + additional_fields=data.get("additional_fields"), waitlist_id=data.get("waitlist_id"), ) except (KeyError, ValueError) as e: @@ -55,7 +58,6 @@ def from_dict(cls, data: dict[str, Any]) -> WaitlistUser: def to_dict(self) -> dict[str, Any]: """Serialize to a dictionary.""" result: dict[str, Any] = {} - result["object"] = self.object result["id"] = self.id result["email"] = self.email result["state"] = ( @@ -67,6 +69,9 @@ def to_dict(self) -> dict[str, Any]: result["approved_at"] = None result["created_at"] = _format_datetime(self.created_at) result["updated_at"] = _format_datetime(self.updated_at) + result["object"] = self.object + if self.additional_fields is not None: + result["additional_fields"] = self.additional_fields if self.waitlist_id is not None: result["waitlist_id"] = self.waitlist_id else: diff --git a/src/workos/common/models/waitlist_user_state.py b/src/workos/common/models/waitlist_user_state.py index 04feeeee..aaa01a5d 100644 --- a/src/workos/common/models/waitlist_user_state.py +++ b/src/workos/common/models/waitlist_user_state.py @@ -1,28 +1,21 @@ # This file is auto-generated by oagen. Do not edit. -"""Enumeration of waitlist user state values.""" +from typing import TYPE_CHECKING -from __future__ import annotations +if TYPE_CHECKING: + from workos.user_management.models.user_management_waitlists_state import ( + UserManagementWaitlistsState as WaitlistUserState, + ) +else: -from enum import Enum -from typing import Literal, TypeAlias + def __getattr__(name: str): + if name == "WaitlistUserState": + from workos.user_management.models.user_management_waitlists_state import ( + UserManagementWaitlistsState, + ) + return UserManagementWaitlistsState + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") -class WaitlistUserState(str, Enum): - """Known values for WaitlistUserState.""" - PENDING = "pending" - APPROVED = "approved" - DENIED = "denied" - - @classmethod - def _missing_(cls, value: object) -> WaitlistUserState | None: - if not isinstance(value, str): - return None - unknown = str.__new__(cls, value) - unknown._name_ = value.upper() - unknown._value_ = value - return unknown - - -WaitlistUserStateLiteral: TypeAlias = Literal["pending", "approved", "denied"] +__all__ = ["WaitlistUserState"] diff --git a/src/workos/events/models/event_schema.py b/src/workos/events/models/event_schema.py index 17326f47..eded15c9 100644 --- a/src/workos/events/models/event_schema.py +++ b/src/workos/events/models/event_schema.py @@ -10,6 +10,17 @@ from workos.common.models.action_user_registration_denied import ( ActionUserRegistrationDenied, ) +from workos.common.models.agent_blueprint_created import AgentBlueprintCreated +from workos.common.models.agent_blueprint_deleted import AgentBlueprintDeleted +from workos.common.models.agent_blueprint_updated import AgentBlueprintUpdated +from workos.common.models.agent_instance_created import AgentInstanceCreated +from workos.common.models.agent_instance_deleted import AgentInstanceDeleted +from workos.common.models.agent_instance_session_created import ( + AgentInstanceSessionCreated, +) +from workos.common.models.agent_instance_session_revoked import ( + AgentInstanceSessionRevoked, +) from workos.common.models.agent_registration_claim_attempt_created import ( AgentRegistrationClaimAttemptCreated, ) @@ -146,6 +157,10 @@ PipesConnectedAccountReauthorizationNeeded, ) from workos.common.models.radar_challenge_created import RadarChallengeCreated +from workos.common.models.resource_export_completed import ResourceExportCompleted +from workos.common.models.resource_export_created import ResourceExportCreated +from workos.common.models.resource_export_downloaded import ResourceExportDownloaded +from workos.common.models.resource_export_failed import ResourceExportFailed from workos.common.models.role_created import RoleCreated from workos.common.models.role_deleted import RoleDeleted from workos.common.models.role_updated import RoleUpdated @@ -193,6 +208,13 @@ def to_dict(self) -> dict[str, Any]: EventSchemaVariant = Union[ ActionAuthenticationDenied, ActionUserRegistrationDenied, + AgentBlueprintCreated, + AgentBlueprintDeleted, + AgentBlueprintUpdated, + AgentInstanceCreated, + AgentInstanceDeleted, + AgentInstanceSessionCreated, + AgentInstanceSessionRevoked, AgentRegistrationClaimAttemptCreated, AgentRegistrationClaimCompleted, AgentRegistrationCreated, @@ -279,6 +301,10 @@ def to_dict(self) -> dict[str, Any]: PipesConnectedAccountDisconnected, PipesConnectedAccountReauthorizationNeeded, RadarChallengeCreated, + ResourceExportCompleted, + ResourceExportCreated, + ResourceExportDownloaded, + ResourceExportFailed, RoleCreated, RoleDeleted, RoleUpdated, @@ -312,6 +338,13 @@ class EventSchema: _DISPATCH: ClassVar[dict[str, type]] = { "action.authentication.denied": ActionAuthenticationDenied, "action.user_registration.denied": ActionUserRegistrationDenied, + "agent.blueprint.created": AgentBlueprintCreated, + "agent.blueprint.deleted": AgentBlueprintDeleted, + "agent.blueprint.updated": AgentBlueprintUpdated, + "agent.instance.created": AgentInstanceCreated, + "agent.instance.deleted": AgentInstanceDeleted, + "agent.instance.session.created": AgentInstanceSessionCreated, + "agent.instance.session.revoked": AgentInstanceSessionRevoked, "agent.registration.claim.attempt.created": AgentRegistrationClaimAttemptCreated, "agent.registration.claim.completed": AgentRegistrationClaimCompleted, "agent.registration.created": AgentRegistrationCreated, @@ -398,6 +431,10 @@ class EventSchema: "pipes.connected_account.disconnected": PipesConnectedAccountDisconnected, "pipes.connected_account.reauthorization_needed": PipesConnectedAccountReauthorizationNeeded, "radar.challenge_created": RadarChallengeCreated, + "resource_export.completed": ResourceExportCompleted, + "resource_export.created": ResourceExportCreated, + "resource_export.downloaded": ResourceExportDownloaded, + "resource_export.failed": ResourceExportFailed, "role.created": RoleCreated, "role.deleted": RoleDeleted, "role.updated": RoleUpdated, diff --git a/src/workos/organizations/_resource.py b/src/workos/organizations/_resource.py index e3b25a78..e1d5671e 100644 --- a/src/workos/organizations/_resource.py +++ b/src/workos/organizations/_resource.py @@ -7,12 +7,15 @@ if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient +from workos.common.models.invite_it_contact_intents import InviteItContactIntents from workos.common.models.pagination_order import PaginationOrder from .._pagination import AsyncPage, SyncPage from .._types import NOT_GIVEN, NotGiven, RequestOptions, enum_value from .models import ( AuditLogConfiguration, + ItContact, + ItContactList, Organization, OrganizationAuthorizedConnectApplicationListData, OrganizationDomainData, @@ -98,7 +101,7 @@ def create_organization( Args: name: The name of the organization. allow_profiles_outside_organization: Whether the organization allows profiles from outside the organization to sign in. - domains: The domains associated with the organization. Deprecated in favor of `domain_data`. + domains: (deprecated) The domains associated with the organization. Deprecated in favor of `domain_data`. domain_data: The domains associated with the organization, including verification state. metadata: Object containing [metadata](https://workos.com/docs/authkit/metadata) key/value pairs associated with the Organization. external_id: An external identifier for the Organization. @@ -371,6 +374,188 @@ def list_authorized_applications( request_options=request_options, ) + def list_it_contacts( + self, + organization_id: str, + *, + request_options: RequestOptions | None = None, + ) -> ItContactList: + """List IT contacts + + Get the IT contacts for an organization. + + Args: + organization_id: The ID of the organization. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ItContactList + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("organizations", str(organization_id), "it_contacts"), + model=ItContactList, + request_options=request_options, + ) + + def create_it_contact( + self, + organization_id: str, + *, + email: str, + request_options: RequestOptions | None = None, + ) -> ItContact: + """Create an IT contact + + Add an IT contact to an organization. No Admin Portal invitation is sent, though the contact is notified if the organization has a connection certificate nearing expiry. + + Args: + organization_id: The ID of the organization. + email: The email address of the IT contact. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ItContact + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + "email": email, + } + return self._client.request( + method="post", + path=("organizations", str(organization_id), "it_contacts"), + body=body, + model=ItContact, + request_options=request_options, + ) + + def delete_it_contact( + self, + organization_id: str, + contact_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an IT contact + + Remove an IT contact from an organization and revoke the contact's active setup links. + + Args: + organization_id: The ID of the organization. + contact_id: The ID of the IT contact. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="delete", + path=( + "organizations", + str(organization_id), + "it_contacts", + str(contact_id), + ), + request_options=request_options, + ) + + def invite_it_contact( + self, + organization_id: str, + contact_id: str, + *, + intents: list[InviteItContactIntents | str], + request_options: RequestOptions | None = None, + ) -> None: + """Invite an IT contact + + Create an Admin Portal setup link and email it to the IT contact. An organization can have at most one active invitation. + + Args: + organization_id: The ID of the organization. + contact_id: The ID of the IT contact. + intents: The Admin Portal features that the IT contact can configure. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + "intents": intents, + } + self._client.request( + method="post", + path=( + "organizations", + str(organization_id), + "it_contacts", + str(contact_id), + "invite", + ), + body=body, + request_options=request_options, + ) + + def revoke_it_contact( + self, + organization_id: str, + contact_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Revoke an IT contact's invitation + + Revoke the organization's active Admin Portal invitation. + + Args: + organization_id: The ID of the organization. + contact_id: The ID of the IT contact. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="post", + path=( + "organizations", + str(organization_id), + "it_contacts", + str(contact_id), + "revoke", + ), + request_options=request_options, + ) + class AsyncOrganizations: """Organizations API resources (async).""" @@ -451,7 +636,7 @@ async def create_organization( Args: name: The name of the organization. allow_profiles_outside_organization: Whether the organization allows profiles from outside the organization to sign in. - domains: The domains associated with the organization. Deprecated in favor of `domain_data`. + domains: (deprecated) The domains associated with the organization. Deprecated in favor of `domain_data`. domain_data: The domains associated with the organization, including verification state. metadata: Object containing [metadata](https://workos.com/docs/authkit/metadata) key/value pairs associated with the Organization. external_id: An external identifier for the Organization. @@ -723,3 +908,185 @@ async def list_authorized_applications( params=params, request_options=request_options, ) + + async def list_it_contacts( + self, + organization_id: str, + *, + request_options: RequestOptions | None = None, + ) -> ItContactList: + """List IT contacts + + Get the IT contacts for an organization. + + Args: + organization_id: The ID of the organization. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ItContactList + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("organizations", str(organization_id), "it_contacts"), + model=ItContactList, + request_options=request_options, + ) + + async def create_it_contact( + self, + organization_id: str, + *, + email: str, + request_options: RequestOptions | None = None, + ) -> ItContact: + """Create an IT contact + + Add an IT contact to an organization. No Admin Portal invitation is sent, though the contact is notified if the organization has a connection certificate nearing expiry. + + Args: + organization_id: The ID of the organization. + email: The email address of the IT contact. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ItContact + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + "email": email, + } + return await self._client.request( + method="post", + path=("organizations", str(organization_id), "it_contacts"), + body=body, + model=ItContact, + request_options=request_options, + ) + + async def delete_it_contact( + self, + organization_id: str, + contact_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an IT contact + + Remove an IT contact from an organization and revoke the contact's active setup links. + + Args: + organization_id: The ID of the organization. + contact_id: The ID of the IT contact. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + await self._client.request( + method="delete", + path=( + "organizations", + str(organization_id), + "it_contacts", + str(contact_id), + ), + request_options=request_options, + ) + + async def invite_it_contact( + self, + organization_id: str, + contact_id: str, + *, + intents: list[InviteItContactIntents | str], + request_options: RequestOptions | None = None, + ) -> None: + """Invite an IT contact + + Create an Admin Portal setup link and email it to the IT contact. An organization can have at most one active invitation. + + Args: + organization_id: The ID of the organization. + contact_id: The ID of the IT contact. + intents: The Admin Portal features that the IT contact can configure. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + "intents": intents, + } + await self._client.request( + method="post", + path=( + "organizations", + str(organization_id), + "it_contacts", + str(contact_id), + "invite", + ), + body=body, + request_options=request_options, + ) + + async def revoke_it_contact( + self, + organization_id: str, + contact_id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Revoke an IT contact's invitation + + Revoke the organization's active Admin Portal invitation. + + Args: + organization_id: The ID of the organization. + contact_id: The ID of the IT contact. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + await self._client.request( + method="post", + path=( + "organizations", + str(organization_id), + "it_contacts", + str(contact_id), + "revoke", + ), + request_options=request_options, + ) diff --git a/src/workos/organizations/models/__init__.py b/src/workos/organizations/models/__init__.py index 5533e02a..247a1c2e 100644 --- a/src/workos/organizations/models/__init__.py +++ b/src/workos/organizations/models/__init__.py @@ -5,6 +5,13 @@ AuditLogConfigurationLogStream as AuditLogConfigurationLogStream, ) from .audit_logs_retention import AuditLogsRetention as AuditLogsRetention +from .create_it_contact import CreateItContact as CreateItContact +from .invite_it_contact import InviteItContact as InviteItContact +from .it_contact import ItContact as ItContact +from .it_contact_list import ItContactList as ItContactList +from .it_contact_list_list_metadata import ( + ItContactListListMetadata as ItContactListListMetadata, +) from .organization import Organization as Organization from .organization_authorized_connect_application_list_data import ( OrganizationAuthorizedConnectApplicationListData as OrganizationAuthorizedConnectApplicationListData, diff --git a/src/workos/organizations/models/create_it_contact.py b/src/workos/organizations/models/create_it_contact.py new file mode 100644 index 00000000..18955b39 --- /dev/null +++ b/src/workos/organizations/models/create_it_contact.py @@ -0,0 +1,32 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class CreateItContact: + """Create It Contact model.""" + + email: str + """The email address of the IT contact.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateItContact: + """Deserialize from a dictionary.""" + try: + return cls( + email=data["email"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateItContact", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["email"] = self.email + return result diff --git a/src/workos/organizations/models/invite_it_contact.py b/src/workos/organizations/models/invite_it_contact.py new file mode 100644 index 00000000..ee00df60 --- /dev/null +++ b/src/workos/organizations/models/invite_it_contact.py @@ -0,0 +1,39 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any, cast + +from workos._types import _raise_deserialize_error +from workos.common.models.invite_it_contact_intents import InviteItContactIntents + + +@dataclass(slots=True) +class InviteItContact: + """Invite It Contact model.""" + + intents: list[InviteItContactIntents] + """The Admin Portal features that the IT contact can configure.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> InviteItContact: + """Deserialize from a dictionary.""" + try: + return cls( + intents=[ + InviteItContactIntents(item) + for item in cast(list[Any], data["intents"]) + ], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("InviteItContact", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["intents"] = [ + item.value if isinstance(item, Enum) else item for item in self.intents + ] + return result diff --git a/src/workos/organizations/models/it_contact.py b/src/workos/organizations/models/it_contact.py new file mode 100644 index 00000000..dde20fba --- /dev/null +++ b/src/workos/organizations/models/it_contact.py @@ -0,0 +1,49 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + + +@dataclass(slots=True) +class ItContact: + """It Contact model.""" + + object: Literal["it_contact"] + """The IT Contact object.""" + id: str + """The unique ID of the IT Contact.""" + email: str + """The email address of the IT Contact.""" + created_at: datetime + """An ISO 8601 timestamp.""" + updated_at: datetime + """An ISO 8601 timestamp.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ItContact: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "it_contact"), + id=data["id"], + email=data["email"], + created_at=_parse_datetime(data["created_at"]), + updated_at=_parse_datetime(data["updated_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ItContact", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["email"] = self.email + result["created_at"] = _format_datetime(self.created_at) + result["updated_at"] = _format_datetime(self.updated_at) + return result diff --git a/src/workos/organizations/models/it_contact_list.py b/src/workos/organizations/models/it_contact_list.py new file mode 100644 index 00000000..da04da52 --- /dev/null +++ b/src/workos/organizations/models/it_contact_list.py @@ -0,0 +1,48 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal, cast + +from workos._types import _raise_deserialize_error + +from .it_contact import ItContact +from .it_contact_list_list_metadata import ItContactListListMetadata + + +@dataclass(slots=True) +class ItContactList: + """It Contact List model.""" + + object: Literal["list"] + """Indicates this is a list response.""" + data: list[ItContact] + """The list of records for the current page.""" + list_metadata: ItContactListListMetadata + """Pagination cursors for navigating between pages of results.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ItContactList: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "list"), + data=[ + ItContact.from_dict(cast(dict[str, Any], item)) + for item in cast(list[Any], data["data"]) + ], + list_metadata=ItContactListListMetadata.from_dict( + cast(dict[str, Any], data["list_metadata"]) + ), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ItContactList", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["data"] = [item.to_dict() for item in self.data] + result["list_metadata"] = self.list_metadata.to_dict() + return result diff --git a/src/workos/organizations/models/it_contact_list_list_metadata.py b/src/workos/organizations/models/it_contact_list_list_metadata.py new file mode 100644 index 00000000..a86aa8d5 --- /dev/null +++ b/src/workos/organizations/models/it_contact_list_list_metadata.py @@ -0,0 +1,42 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class ItContactListListMetadata: + """Pagination cursors for navigating between pages of results.""" + + before: str | None + """An object ID that defines your place in the list. When the ID is not present, you are at the start of the list.""" + after: str | None + """An object ID that defines your place in the list. When the ID is not present, you are at the end of the list.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ItContactListListMetadata: + """Deserialize from a dictionary.""" + try: + return cls( + before=data["before"], + after=data["after"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ItContactListListMetadata", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.before is not None: + result["before"] = self.before + else: + result["before"] = None + if self.after is not None: + result["after"] = self.after + else: + result["after"] = None + return result diff --git a/src/workos/organizations/models/organization_input.py b/src/workos/organizations/models/organization_input.py index e87438f0..d6a3a5f7 100644 --- a/src/workos/organizations/models/organization_input.py +++ b/src/workos/organizations/models/organization_input.py @@ -19,7 +19,9 @@ class OrganizationInput: allow_profiles_outside_organization: bool | None = None """Whether the organization allows profiles from outside the organization to sign in.""" domains: list[str] | None = None - """The domains associated with the organization. Deprecated in favor of `domain_data`.""" + """The domains associated with the organization. Deprecated in favor of `domain_data`. + + .. deprecated:: This field is deprecated.""" domain_data: list[OrganizationDomainData] | None = None """The domains associated with the organization, including verification state.""" metadata: dict[str, str] | None = None diff --git a/src/workos/pipes/models/custom_provider_definition.py b/src/workos/pipes/models/custom_provider_definition.py index 189f6f64..9db8c4c7 100644 --- a/src/workos/pipes/models/custom_provider_definition.py +++ b/src/workos/pipes/models/custom_provider_definition.py @@ -19,11 +19,11 @@ class CustomProviderDefinition: name: str """A descriptive name for the custom provider.""" authorization_url: str | None = None - """The provider's OAuth authorization endpoint. Required for OAuth providers; omit for `api_key` providers.""" + """The provider's OAuth authorization endpoint. Required for OAuth providers; omit for `api_key` providers. Must be a static URL: `${config.…}` placeholders are resolved against a provider's declared config fields, which custom providers cannot declare.""" token_url: str | None = None - """The provider's OAuth token endpoint. Required for OAuth providers; omit for `api_key` providers.""" + """The provider's OAuth token endpoint. Required for OAuth and `client_credentials` providers; omit for `api_key` providers. Must be a static URL: `${config.…}` placeholders are resolved against a provider's declared config fields, which custom providers cannot declare.""" refresh_token_url: str | None = None - """The endpoint used to refresh tokens, if different from the token endpoint.""" + """The endpoint used to refresh tokens, if different from the token endpoint. Must be a static URL, like the other endpoints.""" pkce_enabled: bool | None = None """Whether PKCE is used during the authorization code flow. Defaults to `true`.""" request_scope_separator: str | None = None diff --git a/src/workos/pipes/models/data_integration.py b/src/workos/pipes/models/data_integration.py index 0d00b3cd..69dff813 100644 --- a/src/workos/pipes/models/data_integration.py +++ b/src/workos/pipes/models/data_integration.py @@ -39,11 +39,11 @@ class DataIntegration: scopes: list[str] | None """The OAuth scopes configured for the Data Integration. `null` when the provider's configured scopes are used.""" redirect_uri: str - """The OAuth redirect URI to register with the provider when configuring the custom application.""" + """The OAuth redirect URI to register with the provider when configuring the custom application. Empty for `api_key` and `client_credentials` integrations, which run no authorization redirect.""" auth_methods: list[DataIntegrationAuthMethods] """How accounts authenticate with the provider for this Data Integration.""" credentials: DataIntegrationCredential | None - """The integration-level OAuth app credentials. `null` for `api_key` integrations, which hold no OAuth credentials (keys are installed per-tenant).""" + """The integration-level OAuth app credentials. `null` for `api_key` and `client_credentials` integrations, which hold no integration-level credentials (secrets are installed per-tenant).""" installation: DataIntegrationInstallation | None """The tenant installation created when an API key was supplied at creation time; `null` otherwise. Not populated on list/get responses.""" config: dict[str, str] diff --git a/src/workos/pipes/models/update_custom_provider_definition.py b/src/workos/pipes/models/update_custom_provider_definition.py index 6e2f9999..a865e121 100644 --- a/src/workos/pipes/models/update_custom_provider_definition.py +++ b/src/workos/pipes/models/update_custom_provider_definition.py @@ -19,11 +19,11 @@ class UpdateCustomProviderDefinition: name: str | None = None """A descriptive name for the custom provider.""" authorization_url: str | None = None - """The provider's OAuth authorization endpoint.""" + """The provider's OAuth authorization endpoint. Must be a static URL: `${config.…}` placeholders are resolved against a provider's declared config fields, which custom providers cannot declare.""" token_url: str | None = None - """The provider's OAuth token endpoint.""" + """The provider's OAuth token endpoint. Must be a static URL: `${config.…}` placeholders are resolved against a provider's declared config fields, which custom providers cannot declare.""" refresh_token_url: str | None = None - """The endpoint used to refresh tokens, if different from the token endpoint.""" + """The endpoint used to refresh tokens, if different from the token endpoint. Must be a static URL, like the other endpoints.""" pkce_enabled: bool | None = None """Whether PKCE is used during the authorization code flow.""" request_scope_separator: str | None = None diff --git a/src/workos/platform_teams/__init__.py b/src/workos/platform_teams/__init__.py new file mode 100644 index 00000000..2f113d52 --- /dev/null +++ b/src/workos/platform_teams/__init__.py @@ -0,0 +1,5 @@ +# This file is auto-generated by oagen. Do not edit. + +from ._resource import AsyncPlatformTeams as AsyncPlatformTeams +from ._resource import PlatformTeams as PlatformTeams +from .models import * diff --git a/src/workos/platform_teams/_resource.py b/src/workos/platform_teams/_resource.py new file mode 100644 index 00000000..db768f80 --- /dev/null +++ b/src/workos/platform_teams/_resource.py @@ -0,0 +1,161 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from .._client import AsyncWorkOSClient, WorkOSClient + +from .._types import RequestOptions +from .models import Team + + +class PlatformTeams: + """Platform Teams API resources.""" + + def __init__(self, client: WorkOSClient) -> None: + self._client = client + + def create_team( + self, + *, + admin_email: str, + name: str, + request_options: RequestOptions | None = None, + ) -> Team: + """Create a team + + Creates a team along with its default project, a staging environment, and a production environment. An admin invitation is sent to `admin_email`, onboarding is marked complete with AuthKit enabled, and the calling platform is authorized to act inside the team. + + Args: + admin_email: The email address of the person who will administer the team. An invitation is sent to this address. + name: Name of the team. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + Team + + Raises: + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + "admin_email": admin_email, + "name": name, + } + return self._client.request( + method="post", + path=("platform", "teams"), + body=body, + model=Team, + request_options=request_options, + ) + + def get_team( + self, + team_id: str, + *, + request_options: RequestOptions | None = None, + ) -> Team: + """Get a team + + Returns a team, and doubles as a health check on the platform's access to it. Read `production_state` before attempting to create a production environment. + + Args: + team_id: The ID of the team. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + Team + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("platform", "teams", str(team_id)), + model=Team, + request_options=request_options, + ) + + +class AsyncPlatformTeams: + """Platform Teams API resources (async).""" + + def __init__(self, client: AsyncWorkOSClient) -> None: + self._client = client + + async def create_team( + self, + *, + admin_email: str, + name: str, + request_options: RequestOptions | None = None, + ) -> Team: + """Create a team + + Creates a team along with its default project, a staging environment, and a production environment. An admin invitation is sent to `admin_email`, onboarding is marked complete with AuthKit enabled, and the calling platform is authorized to act inside the team. + + Args: + admin_email: The email address of the person who will administer the team. An invitation is sent to this address. + name: Name of the team. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + Team + + Raises: + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + "admin_email": admin_email, + "name": name, + } + return await self._client.request( + method="post", + path=("platform", "teams"), + body=body, + model=Team, + request_options=request_options, + ) + + async def get_team( + self, + team_id: str, + *, + request_options: RequestOptions | None = None, + ) -> Team: + """Get a team + + Returns a team, and doubles as a health check on the platform's access to it. Read `production_state` before attempting to create a production environment. + + Args: + team_id: The ID of the team. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + Team + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("platform", "teams", str(team_id)), + model=Team, + request_options=request_options, + ) diff --git a/src/workos/platform_teams/models/__init__.py b/src/workos/platform_teams/models/__init__.py new file mode 100644 index 00000000..4d9b5b13 --- /dev/null +++ b/src/workos/platform_teams/models/__init__.py @@ -0,0 +1,4 @@ +# This file is auto-generated by oagen. Do not edit. + +from .create_team import CreateTeam as CreateTeam +from .team import Team as Team diff --git a/src/workos/platform_teams/models/create_team.py b/src/workos/platform_teams/models/create_team.py new file mode 100644 index 00000000..70e804b9 --- /dev/null +++ b/src/workos/platform_teams/models/create_team.py @@ -0,0 +1,36 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class CreateTeam: + """Create Team model.""" + + admin_email: str + """The email address of the person who will administer the team. An invitation is sent to this address.""" + name: str + """Name of the team.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateTeam: + """Deserialize from a dictionary.""" + try: + return cls( + admin_email=data["admin_email"], + name=data["name"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateTeam", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["admin_email"] = self.admin_email + result["name"] = self.name + return result diff --git a/src/workos/platform_teams/models/team.py b/src/workos/platform_teams/models/team.py new file mode 100644 index 00000000..feb07e1a --- /dev/null +++ b/src/workos/platform_teams/models/team.py @@ -0,0 +1,71 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from enum import Enum +from typing import Any, Literal + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error +from workos.common.models.team_production_state import TeamProductionState + + +@dataclass(slots=True) +class Team: + """Team model.""" + + object: Literal["team"] + """Distinguishes the team object.""" + id: str + """Unique identifier of the team.""" + name: str + """The name of the team.""" + production_state: TeamProductionState + """Whether the team can host production environments. `Active` means billing is set up. `Inactive` means a team admin must add a payment method in the WorkOS Dashboard. `Suspended` and `Deleting` mean the team can't be provisioned into.""" + production_enabled_at: datetime | None + """The timestamp when production was enabled for the team, or `null` if it never has been.""" + created_at: datetime + """The timestamp when the team was created.""" + updated_at: datetime + """The timestamp when the team was last updated.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Team: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "team"), + id=data["id"], + name=data["name"], + production_state=TeamProductionState(data["production_state"]), + production_enabled_at=_parse_datetime(_v_production_enabled_at) + if (_v_production_enabled_at := data["production_enabled_at"]) + is not None + else None, + created_at=_parse_datetime(data["created_at"]), + updated_at=_parse_datetime(data["updated_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("Team", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["name"] = self.name + result["production_state"] = ( + self.production_state.value + if isinstance(self.production_state, Enum) + else self.production_state + ) + if self.production_enabled_at is not None: + result["production_enabled_at"] = _format_datetime( + self.production_enabled_at + ) + else: + result["production_enabled_at"] = None + result["created_at"] = _format_datetime(self.created_at) + result["updated_at"] = _format_datetime(self.updated_at) + return result diff --git a/src/workos/sso/models/connections_connection_type.py b/src/workos/sso/models/connections_connection_type.py index 27c252b7..15d38421 100644 --- a/src/workos/sso/models/connections_connection_type.py +++ b/src/workos/sso/models/connections_connection_type.py @@ -22,7 +22,6 @@ class ConnectionsConnectionType(str, Enum): CLASS_LINK_SAML = "ClassLinkSAML" CLEVER_OIDC = "CleverOIDC" CYBER_ARK_SAML = "CyberArkSAML" - DISCORD_OAUTH = "DiscordOAuth" DUO_SAML = "DuoSAML" ENTRA_ID_OIDC = "EntraIdOIDC" GENERIC_OIDC = "GenericOIDC" @@ -32,8 +31,6 @@ class ConnectionsConnectionType(str, Enum): GOOGLE_OAUTH = "GoogleOAuth" GOOGLE_OIDC = "GoogleOIDC" GOOGLE_SAML = "GoogleSAML" - GROK_OAUTH = "GrokOAuth" - XO_AUTH = "XOAuth" INTUIT_OAUTH = "IntuitOAuth" JUMP_CLOUD_SAML = "JumpCloudSAML" KEYCLOAK_SAML = "KeycloakSAML" @@ -84,7 +81,6 @@ def _missing_(cls, value: object) -> ConnectionsConnectionType | None: "ClassLinkSAML", "CleverOIDC", "CyberArkSAML", - "DiscordOAuth", "DuoSAML", "EntraIdOIDC", "GenericOIDC", @@ -94,8 +90,6 @@ def _missing_(cls, value: object) -> ConnectionsConnectionType | None: "GoogleOAuth", "GoogleOIDC", "GoogleSAML", - "GrokOAuth", - "XOAuth", "IntuitOAuth", "JumpCloudSAML", "KeycloakSAML", diff --git a/src/workos/sso/models/create_connection.py b/src/workos/sso/models/create_connection.py new file mode 100644 index 00000000..f7a3f0e2 --- /dev/null +++ b/src/workos/sso/models/create_connection.py @@ -0,0 +1,78 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, cast + +from workos._types import _raise_deserialize_error + +from .create_connection_attribute_maps import CreateConnectionAttributeMaps +from .create_connection_oidc_options import CreateConnectionOIDCOptions +from .create_connection_saml_options import CreateConnectionSAMLOptions + + +@dataclass(slots=True) +class CreateConnection: + """Create Connection model.""" + + organization_id: str + """Unique identifier for the Organization in which the Connection resides.""" + name: str | None = None + """A human-readable name for the Connection. This will most commonly be the organization's name.""" + external_id: str | None = None + """The customer-owned identifier for the Connection.""" + connection_type: str | None = None + """The type of the Connection. Only SAML and OIDC connection types may be created. When omitted, the type is inferred from the provided options.""" + attribute_maps: CreateConnectionAttributeMaps | None = None + """How IdP attributes or claims map onto WorkOS profile fields. Provided fields override the defaults for the connection type.""" + saml_options: CreateConnectionSAMLOptions | None = None + """Protocol configuration for SAML connections. Mutually exclusive with `oidc_options`.""" + oidc_options: CreateConnectionOIDCOptions | None = None + """Protocol configuration for OIDC connections. Mutually exclusive with `saml_options`.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateConnection: + """Deserialize from a dictionary.""" + try: + return cls( + organization_id=data["organization_id"], + name=data.get("name"), + external_id=data.get("external_id"), + connection_type=data.get("connection_type"), + attribute_maps=CreateConnectionAttributeMaps.from_dict( + cast(dict[str, Any], _v_attribute_maps) + ) + if (_v_attribute_maps := data.get("attribute_maps")) is not None + else None, + saml_options=CreateConnectionSAMLOptions.from_dict( + cast(dict[str, Any], _v_saml_options) + ) + if (_v_saml_options := data.get("saml_options")) is not None + else None, + oidc_options=CreateConnectionOIDCOptions.from_dict( + cast(dict[str, Any], _v_oidc_options) + ) + if (_v_oidc_options := data.get("oidc_options")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateConnection", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["organization_id"] = self.organization_id + if self.name is not None: + result["name"] = self.name + if self.external_id is not None: + result["external_id"] = self.external_id + if self.connection_type is not None: + result["connection_type"] = self.connection_type + if self.attribute_maps is not None: + result["attribute_maps"] = self.attribute_maps.to_dict() + if self.saml_options is not None: + result["saml_options"] = self.saml_options.to_dict() + if self.oidc_options is not None: + result["oidc_options"] = self.oidc_options.to_dict() + return result diff --git a/src/workos/sso/models/create_connection_attribute_maps.py b/src/workos/sso/models/create_connection_attribute_maps.py new file mode 100644 index 00000000..f9364d7f --- /dev/null +++ b/src/workos/sso/models/create_connection_attribute_maps.py @@ -0,0 +1,45 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, cast + +from workos._types import _raise_deserialize_error + +from .create_connection_standard_attributes import CreateConnectionStandardAttributes + + +@dataclass(slots=True) +class CreateConnectionAttributeMaps: + """Create Connection Attribute Maps model.""" + + standard_attributes: CreateConnectionStandardAttributes | None = None + """How IdP attributes or claims map onto the standard WorkOS profile fields. Provided fields override the defaults for the connection type.""" + custom_attributes: dict[str, str] | None = None + """How IdP attributes or claims map onto custom attributes, keyed by custom attribute name. Custom attributes must already be defined in the WorkOS dashboard.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateConnectionAttributeMaps: + """Deserialize from a dictionary.""" + try: + return cls( + standard_attributes=CreateConnectionStandardAttributes.from_dict( + cast(dict[str, Any], _v_standard_attributes) + ) + if (_v_standard_attributes := data.get("standard_attributes")) + is not None + else None, + custom_attributes=data.get("custom_attributes"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateConnectionAttributeMaps", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.standard_attributes is not None: + result["standard_attributes"] = self.standard_attributes.to_dict() + if self.custom_attributes is not None: + result["custom_attributes"] = self.custom_attributes + return result diff --git a/src/workos/sso/models/create_connection_key_pair.py b/src/workos/sso/models/create_connection_key_pair.py new file mode 100644 index 00000000..805ec560 --- /dev/null +++ b/src/workos/sso/models/create_connection_key_pair.py @@ -0,0 +1,36 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class CreateConnectionKeyPair: + """Create Connection Key Pair model.""" + + key: str + """The PEM-encoded private key.""" + cert: str + """The PEM-encoded X.509 certificate for the key.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateConnectionKeyPair: + """Deserialize from a dictionary.""" + try: + return cls( + key=data["key"], + cert=data["cert"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateConnectionKeyPair", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["key"] = self.key + result["cert"] = self.cert + return result diff --git a/src/workos/sso/models/create_connection_oidc_options.py b/src/workos/sso/models/create_connection_oidc_options.py new file mode 100644 index 00000000..0f6ef760 --- /dev/null +++ b/src/workos/sso/models/create_connection_oidc_options.py @@ -0,0 +1,115 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any, cast + +from workos._types import _raise_deserialize_error +from workos.common.models.create_connection_oidc_options_id_token_signature_algorithm import ( + CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm, +) +from workos.common.models.create_connection_oidc_options_token_authentication_method import ( + CreateConnectionOIDCOptionsTokenAuthenticationMethod, +) + +from .create_connection_key_pair import CreateConnectionKeyPair + + +@dataclass(slots=True) +class CreateConnectionOIDCOptions: + """Create Connection OIDC Options model.""" + + discovery_endpoint: str + """The OIDC discovery endpoint.""" + client_id: str + """The OIDC client ID.""" + client_secret: str | None = None + """The OIDC client secret. Required for `client_secret_basic` and `client_secret_post`, and rejected for `private_key_jwt`, which authenticates with a key pair instead. This value is write-only and is never returned.""" + redirect_uri: str | None = None + """A custom OAuth callback URL override. When omitted, the standard WorkOS-generated redirect URI is used.""" + pkce: bool | None = None + """Whether PKCE is enabled for the connection.""" + token_authentication_method: ( + CreateConnectionOIDCOptionsTokenAuthenticationMethod | None + ) = None + """The token-endpoint client authentication method.""" + jwt_signing_key_pair: CreateConnectionKeyPair | None = None + """A key pair for WorkOS to sign `private_key_jwt` client assertions with. Only accepted when `token_authentication_method` is `private_key_jwt`; when omitted, WorkOS generates one and returns its certificate in `oidc_options.jwt_signing_certs`.""" + id_token_signature_algorithm: ( + CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm | None + ) = None + """The ID-token signing algorithm.""" + fetch_user_info: bool | None = None + """Whether to fetch additional profile attributes from the userinfo endpoint.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateConnectionOIDCOptions: + """Deserialize from a dictionary.""" + try: + return cls( + discovery_endpoint=data["discovery_endpoint"], + client_id=data["client_id"], + client_secret=data.get("client_secret"), + redirect_uri=data.get("redirect_uri"), + pkce=data.get("pkce"), + token_authentication_method=CreateConnectionOIDCOptionsTokenAuthenticationMethod( + _v_token_authentication_method + ) + if ( + _v_token_authentication_method := data.get( + "token_authentication_method" + ) + ) + is not None + else None, + jwt_signing_key_pair=CreateConnectionKeyPair.from_dict( + cast(dict[str, Any], _v_jwt_signing_key_pair) + ) + if (_v_jwt_signing_key_pair := data.get("jwt_signing_key_pair")) + is not None + else None, + id_token_signature_algorithm=CreateConnectionOIDCOptionsIdTokenSignatureAlgorithm( + _v_id_token_signature_algorithm + ) + if ( + _v_id_token_signature_algorithm := data.get( + "id_token_signature_algorithm" + ) + ) + is not None + else None, + fetch_user_info=data.get("fetch_user_info"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateConnectionOIDCOptions", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["discovery_endpoint"] = self.discovery_endpoint + result["client_id"] = self.client_id + if self.client_secret is not None: + result["client_secret"] = self.client_secret + if self.redirect_uri is not None: + result["redirect_uri"] = self.redirect_uri + if self.pkce is not None: + result["pkce"] = self.pkce + if self.token_authentication_method is not None: + result["token_authentication_method"] = ( + self.token_authentication_method.value + if isinstance(self.token_authentication_method, Enum) + else self.token_authentication_method + ) + if self.jwt_signing_key_pair is not None: + result["jwt_signing_key_pair"] = self.jwt_signing_key_pair.to_dict() + if self.id_token_signature_algorithm is not None: + result["id_token_signature_algorithm"] = ( + self.id_token_signature_algorithm.value + if isinstance(self.id_token_signature_algorithm, Enum) + else self.id_token_signature_algorithm + ) + if self.fetch_user_info is not None: + result["fetch_user_info"] = self.fetch_user_info + return result diff --git a/src/workos/sso/models/create_connection_saml_options.py b/src/workos/sso/models/create_connection_saml_options.py new file mode 100644 index 00000000..fe2ac2cb --- /dev/null +++ b/src/workos/sso/models/create_connection_saml_options.py @@ -0,0 +1,83 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, cast + +from workos._types import _raise_deserialize_error + +from .create_connection_key_pair import CreateConnectionKeyPair + + +@dataclass(slots=True) +class CreateConnectionSAMLOptions: + """Create Connection SAML Options model.""" + + idp_metadata_url: str | None = None + """The Identity Provider metadata URL. When provided, the IdP fields and signing certificates are imported from the metadata document. Mutually exclusive with the manual IdP fields.""" + acs_url: str | None = None + """A custom Assertion Consumer Service (ACS) URL override. When omitted, the standard WorkOS-generated ACS URL is used.""" + sp_entity_id: str | None = None + """A custom Service Provider Entity ID (audience) override. When omitted, the connection external key is used.""" + idp_entity_id: str | None = None + """The Identity Provider Entity ID.""" + idp_sso_url: str | None = None + """The Identity Provider SSO URL. Required when configuring the connection with manual IdP fields.""" + idp_signing_certs: list[str] | None = None + """The X.509 certificates used to verify signed SAML responses from the Identity Provider. Required when configuring the connection with manual IdP fields.""" + sp_signing_key_pair: CreateConnectionKeyPair | None = None + """The customer-owned key pair used to sign SAML requests sent to the Identity Provider. When omitted, WorkOS generates and manages the signing key pair.""" + sp_encryption_key_pairs: list[CreateConnectionKeyPair] | None = None + """The customer-owned key pairs used to decrypt encrypted SAML responses from the Identity Provider. When omitted, WorkOS generates and manages the encryption key pair.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateConnectionSAMLOptions: + """Deserialize from a dictionary.""" + try: + return cls( + idp_metadata_url=data.get("idp_metadata_url"), + acs_url=data.get("acs_url"), + sp_entity_id=data.get("sp_entity_id"), + idp_entity_id=data.get("idp_entity_id"), + idp_sso_url=data.get("idp_sso_url"), + idp_signing_certs=data.get("idp_signing_certs"), + sp_signing_key_pair=CreateConnectionKeyPair.from_dict( + cast(dict[str, Any], _v_sp_signing_key_pair) + ) + if (_v_sp_signing_key_pair := data.get("sp_signing_key_pair")) + is not None + else None, + sp_encryption_key_pairs=[ + CreateConnectionKeyPair.from_dict(cast(dict[str, Any], item)) + for item in cast(list[Any], _v_sp_encryption_key_pairs) + ] + if (_v_sp_encryption_key_pairs := data.get("sp_encryption_key_pairs")) + is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateConnectionSAMLOptions", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.idp_metadata_url is not None: + result["idp_metadata_url"] = self.idp_metadata_url + if self.acs_url is not None: + result["acs_url"] = self.acs_url + if self.sp_entity_id is not None: + result["sp_entity_id"] = self.sp_entity_id + if self.idp_entity_id is not None: + result["idp_entity_id"] = self.idp_entity_id + if self.idp_sso_url is not None: + result["idp_sso_url"] = self.idp_sso_url + if self.idp_signing_certs is not None: + result["idp_signing_certs"] = self.idp_signing_certs + if self.sp_signing_key_pair is not None: + result["sp_signing_key_pair"] = self.sp_signing_key_pair.to_dict() + if self.sp_encryption_key_pairs is not None: + result["sp_encryption_key_pairs"] = [ + item.to_dict() for item in self.sp_encryption_key_pairs + ] + return result diff --git a/src/workos/sso/models/create_connection_standard_attributes.py b/src/workos/sso/models/create_connection_standard_attributes.py new file mode 100644 index 00000000..812428d6 --- /dev/null +++ b/src/workos/sso/models/create_connection_standard_attributes.py @@ -0,0 +1,62 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class CreateConnectionStandardAttributes: + """Create Connection Standard Attributes model.""" + + idp_id: str | None = None + """The IdP attribute or claim the profile's `idp_id` is mapped from. When omitted, the default for the connection type is used.""" + email: str | None = None + """The IdP attribute or claim the profile's `email` is mapped from. When omitted, the default for the connection type is used.""" + first_name: str | None = None + """The IdP attribute or claim the profile's `first_name` is mapped from. When omitted, the default for the connection type is used.""" + last_name: str | None = None + """The IdP attribute or claim the profile's `last_name` is mapped from. When omitted, the default for the connection type is used.""" + groups: str | None = None + """The IdP attribute or claim the profile's `groups` are mapped from. `null` leaves the mapping unset.""" + name: str | None = None + """The IdP attribute or claim the profile's `name` is mapped from. `null` leaves the mapping unset.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateConnectionStandardAttributes: + """Deserialize from a dictionary.""" + try: + return cls( + idp_id=data.get("idp_id"), + email=data.get("email"), + first_name=data.get("first_name"), + last_name=data.get("last_name"), + groups=data.get("groups"), + name=data.get("name"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateConnectionStandardAttributes", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.idp_id is not None: + result["idp_id"] = self.idp_id + if self.email is not None: + result["email"] = self.email + if self.first_name is not None: + result["first_name"] = self.first_name + if self.last_name is not None: + result["last_name"] = self.last_name + if self.groups is not None: + result["groups"] = self.groups + else: + result["groups"] = None + if self.name is not None: + result["name"] = self.name + else: + result["name"] = None + return result diff --git a/src/workos/sso/models/patch_connection.py b/src/workos/sso/models/patch_connection.py new file mode 100644 index 00000000..3d13d0c8 --- /dev/null +++ b/src/workos/sso/models/patch_connection.py @@ -0,0 +1,76 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, cast + +from workos._types import _raise_deserialize_error + +from .patch_connection_attribute_maps import PatchConnectionAttributeMaps +from .patch_connection_oidc_options import PatchConnectionOIDCOptions +from .patch_connection_saml_options import PatchConnectionSAMLOptions + + +@dataclass(slots=True) +class PatchConnection: + """Patch Connection model.""" + + name: str | None = None + """A human-readable name for the Connection.""" + external_id: str | None = None + """The customer-owned identifier for the Connection. Set to `null` to stop tracking one.""" + connection_type: str | None = None + """The type of the Connection. Immutable after creation — it may be sent, but only with the Connection current type.""" + attribute_maps: PatchConnectionAttributeMaps | None = None + """How IdP attributes or claims map onto WorkOS profile fields. Only the provided fields are updated.""" + saml_options: PatchConnectionSAMLOptions | None = None + """Protocol configuration for SAML connections. Only the provided fields are updated. Mutually exclusive with `oidc_options`.""" + oidc_options: PatchConnectionOIDCOptions | None = None + """Protocol configuration for OIDC connections. Only the provided fields are updated. Mutually exclusive with `saml_options`.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> PatchConnection: + """Deserialize from a dictionary.""" + try: + return cls( + name=data.get("name"), + external_id=data.get("external_id"), + connection_type=data.get("connection_type"), + attribute_maps=PatchConnectionAttributeMaps.from_dict( + cast(dict[str, Any], _v_attribute_maps) + ) + if (_v_attribute_maps := data.get("attribute_maps")) is not None + else None, + saml_options=PatchConnectionSAMLOptions.from_dict( + cast(dict[str, Any], _v_saml_options) + ) + if (_v_saml_options := data.get("saml_options")) is not None + else None, + oidc_options=PatchConnectionOIDCOptions.from_dict( + cast(dict[str, Any], _v_oidc_options) + ) + if (_v_oidc_options := data.get("oidc_options")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("PatchConnection", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.name is not None: + result["name"] = self.name + if self.external_id is not None: + result["external_id"] = self.external_id + else: + result["external_id"] = None + if self.connection_type is not None: + result["connection_type"] = self.connection_type + if self.attribute_maps is not None: + result["attribute_maps"] = self.attribute_maps.to_dict() + if self.saml_options is not None: + result["saml_options"] = self.saml_options.to_dict() + if self.oidc_options is not None: + result["oidc_options"] = self.oidc_options.to_dict() + return result diff --git a/src/workos/sso/models/patch_connection_attribute_maps.py b/src/workos/sso/models/patch_connection_attribute_maps.py new file mode 100644 index 00000000..bc27b806 --- /dev/null +++ b/src/workos/sso/models/patch_connection_attribute_maps.py @@ -0,0 +1,45 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, cast + +from workos._types import _raise_deserialize_error + +from .patch_connection_standard_attributes import PatchConnectionStandardAttributes + + +@dataclass(slots=True) +class PatchConnectionAttributeMaps: + """Patch Connection Attribute Maps model.""" + + standard_attributes: PatchConnectionStandardAttributes | None = None + """How IdP attributes or claims map onto the standard WorkOS profile fields. Only the provided fields are updated.""" + custom_attributes: dict[str, str | None] | None = None + """How IdP attributes or claims map onto custom attributes, keyed by custom attribute name. Custom attributes must already be defined in the WorkOS dashboard. Only the provided keys are updated; a `null` value unsets that mapping.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> PatchConnectionAttributeMaps: + """Deserialize from a dictionary.""" + try: + return cls( + standard_attributes=PatchConnectionStandardAttributes.from_dict( + cast(dict[str, Any], _v_standard_attributes) + ) + if (_v_standard_attributes := data.get("standard_attributes")) + is not None + else None, + custom_attributes=data.get("custom_attributes"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("PatchConnectionAttributeMaps", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.standard_attributes is not None: + result["standard_attributes"] = self.standard_attributes.to_dict() + if self.custom_attributes is not None: + result["custom_attributes"] = self.custom_attributes + return result diff --git a/src/workos/sso/models/patch_connection_oidc_options.py b/src/workos/sso/models/patch_connection_oidc_options.py new file mode 100644 index 00000000..de170e4f --- /dev/null +++ b/src/workos/sso/models/patch_connection_oidc_options.py @@ -0,0 +1,107 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any + +from workos._types import _raise_deserialize_error +from workos.common.models.patch_connection_oidc_options_id_token_signature_algorithm import ( + PatchConnectionOIDCOptionsIdTokenSignatureAlgorithm, +) +from workos.common.models.patch_connection_oidc_options_token_authentication_method import ( + PatchConnectionOIDCOptionsTokenAuthenticationMethod, +) + + +@dataclass(slots=True) +class PatchConnectionOIDCOptions: + """Patch Connection OIDC Options model.""" + + discovery_endpoint: str | None = None + """The OIDC discovery endpoint.""" + client_id: str | None = None + """The OIDC client ID.""" + client_secret: str | None = None + """The OIDC client secret. Required when moving the connection to `client_secret_basic` or `client_secret_post`, and rejected for `private_key_jwt`, which authenticates with a key pair instead. This value is write-only and is never returned.""" + redirect_uri: str | None = None + """A custom OAuth callback URL override. Set to `null` to revert to the standard WorkOS-generated redirect URI.""" + pkce: bool | None = None + """Whether PKCE is enabled for the connection.""" + token_authentication_method: ( + PatchConnectionOIDCOptionsTokenAuthenticationMethod | None + ) = None + """The token-endpoint client authentication method. Moving to `private_key_jwt` generates a signing key pair if the connection has none; its certificate is returned in `oidc_options.jwt_signing_certs` and must be registered at the Identity Provider.""" + id_token_signature_algorithm: ( + PatchConnectionOIDCOptionsIdTokenSignatureAlgorithm | None + ) = None + """The ID-token signing algorithm.""" + fetch_user_info: bool | None = None + """Whether to fetch additional profile attributes from the userinfo endpoint.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> PatchConnectionOIDCOptions: + """Deserialize from a dictionary.""" + try: + return cls( + discovery_endpoint=data.get("discovery_endpoint"), + client_id=data.get("client_id"), + client_secret=data.get("client_secret"), + redirect_uri=data.get("redirect_uri"), + pkce=data.get("pkce"), + token_authentication_method=PatchConnectionOIDCOptionsTokenAuthenticationMethod( + _v_token_authentication_method + ) + if ( + _v_token_authentication_method := data.get( + "token_authentication_method" + ) + ) + is not None + else None, + id_token_signature_algorithm=PatchConnectionOIDCOptionsIdTokenSignatureAlgorithm( + _v_id_token_signature_algorithm + ) + if ( + _v_id_token_signature_algorithm := data.get( + "id_token_signature_algorithm" + ) + ) + is not None + else None, + fetch_user_info=data.get("fetch_user_info"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("PatchConnectionOIDCOptions", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.discovery_endpoint is not None: + result["discovery_endpoint"] = self.discovery_endpoint + if self.client_id is not None: + result["client_id"] = self.client_id + if self.client_secret is not None: + result["client_secret"] = self.client_secret + if self.redirect_uri is not None: + result["redirect_uri"] = self.redirect_uri + else: + result["redirect_uri"] = None + if self.pkce is not None: + result["pkce"] = self.pkce + if self.token_authentication_method is not None: + result["token_authentication_method"] = ( + self.token_authentication_method.value + if isinstance(self.token_authentication_method, Enum) + else self.token_authentication_method + ) + if self.id_token_signature_algorithm is not None: + result["id_token_signature_algorithm"] = ( + self.id_token_signature_algorithm.value + if isinstance(self.id_token_signature_algorithm, Enum) + else self.id_token_signature_algorithm + ) + if self.fetch_user_info is not None: + result["fetch_user_info"] = self.fetch_user_info + return result diff --git a/src/workos/sso/models/patch_connection_saml_options.py b/src/workos/sso/models/patch_connection_saml_options.py new file mode 100644 index 00000000..9511cd10 --- /dev/null +++ b/src/workos/sso/models/patch_connection_saml_options.py @@ -0,0 +1,59 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class PatchConnectionSAMLOptions: + """Patch Connection SAML Options model.""" + + idp_metadata_url: str | None = None + """The Identity Provider metadata URL. When provided, the IdP fields and signing certificates are re-imported from the metadata document, replacing the current set. Mutually exclusive with the manual IdP fields. Set to `null` to stop tracking a metadata URL.""" + acs_url: str | None = None + """A custom Assertion Consumer Service (ACS) URL override. Set to `null` to revert to the standard WorkOS-generated ACS URL.""" + sp_entity_id: str | None = None + """A custom Service Provider Entity ID (audience) override. Set to `null` to revert to the connection external key.""" + idp_entity_id: str | None = None + """The Identity Provider Entity ID.""" + idp_sso_url: str | None = None + """The Identity Provider SSO URL.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> PatchConnectionSAMLOptions: + """Deserialize from a dictionary.""" + try: + return cls( + idp_metadata_url=data.get("idp_metadata_url"), + acs_url=data.get("acs_url"), + sp_entity_id=data.get("sp_entity_id"), + idp_entity_id=data.get("idp_entity_id"), + idp_sso_url=data.get("idp_sso_url"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("PatchConnectionSAMLOptions", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.idp_metadata_url is not None: + result["idp_metadata_url"] = self.idp_metadata_url + else: + result["idp_metadata_url"] = None + if self.acs_url is not None: + result["acs_url"] = self.acs_url + else: + result["acs_url"] = None + if self.sp_entity_id is not None: + result["sp_entity_id"] = self.sp_entity_id + else: + result["sp_entity_id"] = None + if self.idp_entity_id is not None: + result["idp_entity_id"] = self.idp_entity_id + if self.idp_sso_url is not None: + result["idp_sso_url"] = self.idp_sso_url + return result diff --git a/src/workos/sso/models/patch_connection_standard_attributes.py b/src/workos/sso/models/patch_connection_standard_attributes.py new file mode 100644 index 00000000..c37fee85 --- /dev/null +++ b/src/workos/sso/models/patch_connection_standard_attributes.py @@ -0,0 +1,7 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .create_connection_standard_attributes import CreateConnectionStandardAttributes + +PatchConnectionStandardAttributes: TypeAlias = CreateConnectionStandardAttributes diff --git a/src/workos/types/platform_teams/__init__.py b/src/workos/types/platform_teams/__init__.py new file mode 100644 index 00000000..33c3391e --- /dev/null +++ b/src/workos/types/platform_teams/__init__.py @@ -0,0 +1,3 @@ +# This file is auto-generated by oagen. Do not edit. + +from workos.platform_teams.models import * diff --git a/tests/fixtures/agent_blueprint.json b/tests/fixtures/agent_blueprint.json new file mode 100644 index 00000000..942ec8f2 --- /dev/null +++ b/tests/fixtures/agent_blueprint.json @@ -0,0 +1,25 @@ +{ + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": [ + "crm:read", + "email:send" + ], + "invocable_by": { + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/agent_blueprint_created.json b/tests/fixtures/agent_blueprint_created.json new file mode 100644 index 00000000..a5dbe363 --- /dev/null +++ b/tests/fixtures/agent_blueprint_created.json @@ -0,0 +1,51 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.blueprint.created", + "data": { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": [ + "crm:read", + "email:send" + ], + "invocable_by": { + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/agent_blueprint_created_data.json b/tests/fixtures/agent_blueprint_created_data.json new file mode 100644 index 00000000..942ec8f2 --- /dev/null +++ b/tests/fixtures/agent_blueprint_created_data.json @@ -0,0 +1,25 @@ +{ + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": [ + "crm:read", + "email:send" + ], + "invocable_by": { + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/agent_blueprint_created_data_invocable_by.json b/tests/fixtures/agent_blueprint_created_data_invocable_by.json new file mode 100644 index 00000000..975d5c5f --- /dev/null +++ b/tests/fixtures/agent_blueprint_created_data_invocable_by.json @@ -0,0 +1,8 @@ +{ + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] +} diff --git a/tests/fixtures/agent_blueprint_created_data_session_setting.json b/tests/fixtures/agent_blueprint_created_data_session_setting.json new file mode 100644 index 00000000..6bdcfca4 --- /dev/null +++ b/tests/fixtures/agent_blueprint_created_data_session_setting.json @@ -0,0 +1,5 @@ +{ + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 +} diff --git a/tests/fixtures/agent_blueprint_deleted.json b/tests/fixtures/agent_blueprint_deleted.json new file mode 100644 index 00000000..20e639e5 --- /dev/null +++ b/tests/fixtures/agent_blueprint_deleted.json @@ -0,0 +1,33 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.blueprint.deleted", + "data": { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/agent_blueprint_deleted_data.json b/tests/fixtures/agent_blueprint_deleted_data.json new file mode 100644 index 00000000..b2855874 --- /dev/null +++ b/tests/fixtures/agent_blueprint_deleted_data.json @@ -0,0 +1,7 @@ +{ + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/agent_blueprint_invocable_by.json b/tests/fixtures/agent_blueprint_invocable_by.json new file mode 100644 index 00000000..975d5c5f --- /dev/null +++ b/tests/fixtures/agent_blueprint_invocable_by.json @@ -0,0 +1,8 @@ +{ + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] +} diff --git a/tests/fixtures/agent_blueprint_session_setting.json b/tests/fixtures/agent_blueprint_session_setting.json new file mode 100644 index 00000000..6bdcfca4 --- /dev/null +++ b/tests/fixtures/agent_blueprint_session_setting.json @@ -0,0 +1,5 @@ +{ + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 +} diff --git a/tests/fixtures/agent_blueprint_updated.json b/tests/fixtures/agent_blueprint_updated.json new file mode 100644 index 00000000..72b8b717 --- /dev/null +++ b/tests/fixtures/agent_blueprint_updated.json @@ -0,0 +1,51 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.blueprint.updated", + "data": { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": [ + "crm:read", + "email:send" + ], + "invocable_by": { + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/agent_blueprint_updated_data.json b/tests/fixtures/agent_blueprint_updated_data.json new file mode 100644 index 00000000..942ec8f2 --- /dev/null +++ b/tests/fixtures/agent_blueprint_updated_data.json @@ -0,0 +1,25 @@ +{ + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": [ + "crm:read", + "email:send" + ], + "invocable_by": { + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/agent_blueprint_updated_data_invocable_by.json b/tests/fixtures/agent_blueprint_updated_data_invocable_by.json new file mode 100644 index 00000000..975d5c5f --- /dev/null +++ b/tests/fixtures/agent_blueprint_updated_data_invocable_by.json @@ -0,0 +1,8 @@ +{ + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] +} diff --git a/tests/fixtures/agent_blueprint_updated_data_session_setting.json b/tests/fixtures/agent_blueprint_updated_data_session_setting.json new file mode 100644 index 00000000..6bdcfca4 --- /dev/null +++ b/tests/fixtures/agent_blueprint_updated_data_session_setting.json @@ -0,0 +1,5 @@ +{ + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 +} diff --git a/tests/fixtures/agent_blueprints_create_request.json b/tests/fixtures/agent_blueprints_create_request.json new file mode 100644 index 00000000..f7a2ddd1 --- /dev/null +++ b/tests/fixtures/agent_blueprints_create_request.json @@ -0,0 +1,21 @@ +{ + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": [ + "crm:read", + "email:send" + ], + "invocable_by": { + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 + } +} diff --git a/tests/fixtures/agent_blueprints_create_request_invocable_by.json b/tests/fixtures/agent_blueprints_create_request_invocable_by.json new file mode 100644 index 00000000..975d5c5f --- /dev/null +++ b/tests/fixtures/agent_blueprints_create_request_invocable_by.json @@ -0,0 +1,8 @@ +{ + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] +} diff --git a/tests/fixtures/agent_blueprints_create_request_session_setting.json b/tests/fixtures/agent_blueprints_create_request_session_setting.json new file mode 100644 index 00000000..6bdcfca4 --- /dev/null +++ b/tests/fixtures/agent_blueprints_create_request_session_setting.json @@ -0,0 +1,5 @@ +{ + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 +} diff --git a/tests/fixtures/agent_blueprints_token_mint_token_request.json b/tests/fixtures/agent_blueprints_token_mint_token_request.json new file mode 100644 index 00000000..f9baf5ac --- /dev/null +++ b/tests/fixtures/agent_blueprints_token_mint_token_request.json @@ -0,0 +1,8 @@ +{ + "type": "user_delegated", + "user_access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...", + "intent": "renew-contract-123", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...", + "refresh_token": "njGkA8Wyht0GBEGGA0Zh1Q3wZzL2..." +} diff --git a/tests/fixtures/agent_blueprints_update_request.json b/tests/fixtures/agent_blueprints_update_request.json new file mode 100644 index 00000000..f7a2ddd1 --- /dev/null +++ b/tests/fixtures/agent_blueprints_update_request.json @@ -0,0 +1,21 @@ +{ + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": [ + "crm:read", + "email:send" + ], + "invocable_by": { + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 + } +} diff --git a/tests/fixtures/agent_blueprints_update_request_invocable_by.json b/tests/fixtures/agent_blueprints_update_request_invocable_by.json new file mode 100644 index 00000000..975d5c5f --- /dev/null +++ b/tests/fixtures/agent_blueprints_update_request_invocable_by.json @@ -0,0 +1,8 @@ +{ + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] +} diff --git a/tests/fixtures/agent_blueprints_update_request_session_setting.json b/tests/fixtures/agent_blueprints_update_request_session_setting.json new file mode 100644 index 00000000..6bdcfca4 --- /dev/null +++ b/tests/fixtures/agent_blueprints_update_request_session_setting.json @@ -0,0 +1,5 @@ +{ + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 +} diff --git a/tests/fixtures/agent_instance.json b/tests/fixtures/agent_instance.json new file mode 100644 index 00000000..eca3272d --- /dev/null +++ b/tests/fixtures/agent_instance.json @@ -0,0 +1,10 @@ +{ + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/agent_instance_created.json b/tests/fixtures/agent_instance_created.json new file mode 100644 index 00000000..dc6dbe36 --- /dev/null +++ b/tests/fixtures/agent_instance_created.json @@ -0,0 +1,36 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.created", + "data": { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/agent_instance_created_data.json b/tests/fixtures/agent_instance_created_data.json new file mode 100644 index 00000000..eca3272d --- /dev/null +++ b/tests/fixtures/agent_instance_created_data.json @@ -0,0 +1,10 @@ +{ + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/agent_instance_deleted.json b/tests/fixtures/agent_instance_deleted.json new file mode 100644 index 00000000..6a381618 --- /dev/null +++ b/tests/fixtures/agent_instance_deleted.json @@ -0,0 +1,36 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.deleted", + "data": { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/agent_instance_deleted_data.json b/tests/fixtures/agent_instance_deleted_data.json new file mode 100644 index 00000000..eca3272d --- /dev/null +++ b/tests/fixtures/agent_instance_deleted_data.json @@ -0,0 +1,10 @@ +{ + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/agent_instance_session.json b/tests/fixtures/agent_instance_session.json new file mode 100644 index 00000000..71ec3716 --- /dev/null +++ b/tests/fixtures/agent_instance_session.json @@ -0,0 +1,10 @@ +{ + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "status": "active", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": null, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/agent_instance_session_created.json b/tests/fixtures/agent_instance_session_created.json new file mode 100644 index 00000000..a45440eb --- /dev/null +++ b/tests/fixtures/agent_instance_session_created.json @@ -0,0 +1,39 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.session.created", + "data": { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": null, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "permission_slugs": [ + "crm:read" + ] + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/agent_instance_session_created_data.json b/tests/fixtures/agent_instance_session_created_data.json new file mode 100644 index 00000000..34db08c4 --- /dev/null +++ b/tests/fixtures/agent_instance_session_created_data.json @@ -0,0 +1,13 @@ +{ + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": null, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "permission_slugs": [ + "crm:read" + ] +} diff --git a/tests/fixtures/agent_instance_session_revoked.json b/tests/fixtures/agent_instance_session_revoked.json new file mode 100644 index 00000000..50751650 --- /dev/null +++ b/tests/fixtures/agent_instance_session_revoked.json @@ -0,0 +1,36 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.session.revoked", + "data": { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": null, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/agent_instance_session_revoked_data.json b/tests/fixtures/agent_instance_session_revoked_data.json new file mode 100644 index 00000000..ae64a719 --- /dev/null +++ b/tests/fixtures/agent_instance_session_revoked_data.json @@ -0,0 +1,10 @@ +{ + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": null, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/agent_token.json b/tests/fixtures/agent_token.json new file mode 100644 index 00000000..8d6540f6 --- /dev/null +++ b/tests/fixtures/agent_token.json @@ -0,0 +1,12 @@ +{ + "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...", + "token_type": "Bearer", + "expires_in": 300, + "refresh_token": "njGkA8Wyht0GBEGGA0Zh1Q3wZzL2...", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "new_instance": false, + "agent_instance_session_id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "permissions": [ + "crm:read" + ] +} diff --git a/tests/fixtures/authentication_oauth_failed.json b/tests/fixtures/authentication_oauth_failed.json index d79d92bf..75ae2dfc 100644 --- a/tests/fixtures/authentication_oauth_failed.json +++ b/tests/fixtures/authentication_oauth_failed.json @@ -12,7 +12,8 @@ "error": { "code": "mfa_challenge_failed", "message": "The MFA challenge has failed." - } + }, + "provider": "GoogleOAuth" }, "created_at": "2026-01-15T12:00:00.000Z", "context": { diff --git a/tests/fixtures/authentication_oauth_failed_data.json b/tests/fixtures/authentication_oauth_failed_data.json index 46d3a614..2df3c767 100644 --- a/tests/fixtures/authentication_oauth_failed_data.json +++ b/tests/fixtures/authentication_oauth_failed_data.json @@ -8,5 +8,6 @@ "error": { "code": "mfa_challenge_failed", "message": "The MFA challenge has failed." - } + }, + "provider": "GoogleOAuth" } diff --git a/tests/fixtures/authentication_oauth_succeeded.json b/tests/fixtures/authentication_oauth_succeeded.json index 9002eb8d..0c78873e 100644 --- a/tests/fixtures/authentication_oauth_succeeded.json +++ b/tests/fixtures/authentication_oauth_succeeded.json @@ -8,7 +8,8 @@ "ip_address": "203.0.113.42", "user_agent": "Mozilla/5.0", "user_id": "user_01E4ZCR3C56J083X43JQXF3JK5", - "email": "user@example.com" + "email": "user@example.com", + "provider": "GoogleOAuth" }, "created_at": "2026-01-15T12:00:00.000Z", "context": { diff --git a/tests/fixtures/authentication_oauth_succeeded_data.json b/tests/fixtures/authentication_oauth_succeeded_data.json index 437beddd..636be786 100644 --- a/tests/fixtures/authentication_oauth_succeeded_data.json +++ b/tests/fixtures/authentication_oauth_succeeded_data.json @@ -4,5 +4,6 @@ "ip_address": "203.0.113.42", "user_agent": "Mozilla/5.0", "user_id": "user_01E4ZCR3C56J083X43JQXF3JK5", - "email": "user@example.com" + "email": "user@example.com", + "provider": "GoogleOAuth" } diff --git a/tests/fixtures/create_connection.json b/tests/fixtures/create_connection.json new file mode 100644 index 00000000..74bc8245 --- /dev/null +++ b/tests/fixtures/create_connection.json @@ -0,0 +1,53 @@ +{ + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Foo Corp", + "external_id": "acme-legacy-conn-42", + "connection_type": "OktaSAML", + "attribute_maps": { + "standard_attributes": { + "idp_id": "sub", + "email": "email", + "first_name": "given_name", + "last_name": "family_name", + "groups": "memberOf", + "name": "displayName" + }, + "custom_attributes": { + "company": "company_claim" + } + }, + "saml_options": { + "idp_metadata_url": "https://idp.example.com/metadata.xml", + "acs_url": "https://example.auth0.com/login/callback?connection=123", + "sp_entity_id": "https://example.auth0.com/login/callback?connection=123", + "idp_entity_id": "https://idp.example.com/entity", + "idp_sso_url": "https://idp.example.com/sso", + "idp_signing_certs": [ + "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" + ], + "sp_signing_key_pair": { + "key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----", + "cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" + }, + "sp_encryption_key_pairs": [ + { + "key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----", + "cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" + } + ] + }, + "oidc_options": { + "discovery_endpoint": "https://idp.example.com/.well-known/openid-configuration", + "client_id": "client_123", + "client_secret": "secret_xyz", + "redirect_uri": "https://auth.workos.com/sso/oidc/conn_externalkey/callback", + "pkce": true, + "token_authentication_method": "client_secret_basic", + "jwt_signing_key_pair": { + "key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----", + "cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" + }, + "id_token_signature_algorithm": "RS256", + "fetch_user_info": false + } +} diff --git a/tests/fixtures/create_connection_attribute_maps.json b/tests/fixtures/create_connection_attribute_maps.json new file mode 100644 index 00000000..50dccfe6 --- /dev/null +++ b/tests/fixtures/create_connection_attribute_maps.json @@ -0,0 +1,13 @@ +{ + "standard_attributes": { + "idp_id": "sub", + "email": "email", + "first_name": "given_name", + "last_name": "family_name", + "groups": "memberOf", + "name": "displayName" + }, + "custom_attributes": { + "company": "company_claim" + } +} diff --git a/tests/fixtures/create_connection_key_pair.json b/tests/fixtures/create_connection_key_pair.json new file mode 100644 index 00000000..d4397149 --- /dev/null +++ b/tests/fixtures/create_connection_key_pair.json @@ -0,0 +1,4 @@ +{ + "key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----", + "cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" +} diff --git a/tests/fixtures/create_connection_oidc_options.json b/tests/fixtures/create_connection_oidc_options.json new file mode 100644 index 00000000..223063f4 --- /dev/null +++ b/tests/fixtures/create_connection_oidc_options.json @@ -0,0 +1,14 @@ +{ + "discovery_endpoint": "https://idp.example.com/.well-known/openid-configuration", + "client_id": "client_123", + "client_secret": "secret_xyz", + "redirect_uri": "https://auth.workos.com/sso/oidc/conn_externalkey/callback", + "pkce": true, + "token_authentication_method": "client_secret_basic", + "jwt_signing_key_pair": { + "key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----", + "cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" + }, + "id_token_signature_algorithm": "RS256", + "fetch_user_info": false +} diff --git a/tests/fixtures/create_connection_saml_options.json b/tests/fixtures/create_connection_saml_options.json new file mode 100644 index 00000000..6a6e7427 --- /dev/null +++ b/tests/fixtures/create_connection_saml_options.json @@ -0,0 +1,20 @@ +{ + "idp_metadata_url": "https://idp.example.com/metadata.xml", + "acs_url": "https://example.auth0.com/login/callback?connection=123", + "sp_entity_id": "https://example.auth0.com/login/callback?connection=123", + "idp_entity_id": "https://idp.example.com/entity", + "idp_sso_url": "https://idp.example.com/sso", + "idp_signing_certs": [ + "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" + ], + "sp_signing_key_pair": { + "key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----", + "cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" + }, + "sp_encryption_key_pairs": [ + { + "key": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----", + "cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" + } + ] +} diff --git a/tests/fixtures/create_connection_standard_attributes.json b/tests/fixtures/create_connection_standard_attributes.json new file mode 100644 index 00000000..026736a9 --- /dev/null +++ b/tests/fixtures/create_connection_standard_attributes.json @@ -0,0 +1,8 @@ +{ + "idp_id": "sub", + "email": "email", + "first_name": "given_name", + "last_name": "family_name", + "groups": "memberOf", + "name": "displayName" +} diff --git a/tests/fixtures/create_it_contact.json b/tests/fixtures/create_it_contact.json new file mode 100644 index 00000000..6de60fb5 --- /dev/null +++ b/tests/fixtures/create_it_contact.json @@ -0,0 +1,3 @@ +{ + "email": "it-contact@example.com" +} diff --git a/tests/fixtures/create_saml_idp_signing_certificate.json b/tests/fixtures/create_saml_idp_signing_certificate.json new file mode 100644 index 00000000..e1d6c144 --- /dev/null +++ b/tests/fixtures/create_saml_idp_signing_certificate.json @@ -0,0 +1,3 @@ +{ + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----" +} diff --git a/tests/fixtures/create_team.json b/tests/fixtures/create_team.json new file mode 100644 index 00000000..dc65d152 --- /dev/null +++ b/tests/fixtures/create_team.json @@ -0,0 +1,4 @@ +{ + "admin_email": "alice@example.com", + "name": "Example Team" +} diff --git a/tests/fixtures/create_waitlist_entry.json b/tests/fixtures/create_waitlist_entry.json new file mode 100644 index 00000000..c53e1ff7 --- /dev/null +++ b/tests/fixtures/create_waitlist_entry.json @@ -0,0 +1,7 @@ +{ + "email": "marcelina.davis@example.com", + "additional_fields": { + "company": "Example Corp" + }, + "send_confirmation_email": false +} diff --git a/tests/fixtures/data_integrations_upsert_client_credentials_request.json b/tests/fixtures/data_integrations_upsert_client_credentials_request.json index 51b5a18e..1dc0fac0 100644 --- a/tests/fixtures/data_integrations_upsert_client_credentials_request.json +++ b/tests/fixtures/data_integrations_upsert_client_credentials_request.json @@ -4,6 +4,6 @@ "client_id": "3MVG9...", "client_secret": "shhh-secret", "config": { - "mydomain": "acme" + "salesforce_host": "acme.my.salesforce.com" } } diff --git a/tests/fixtures/invite_it_contact.json b/tests/fixtures/invite_it_contact.json new file mode 100644 index 00000000..a5dfeb78 --- /dev/null +++ b/tests/fixtures/invite_it_contact.json @@ -0,0 +1,6 @@ +{ + "intents": [ + "sso", + "directory_sync" + ] +} diff --git a/tests/fixtures/it_contact.json b/tests/fixtures/it_contact.json new file mode 100644 index 00000000..f5759748 --- /dev/null +++ b/tests/fixtures/it_contact.json @@ -0,0 +1,7 @@ +{ + "object": "it_contact", + "id": "it_contact_01HXYZ123456789ABCDEFGHIJ", + "email": "it-contact@example.com", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/it_contact_list.json b/tests/fixtures/it_contact_list.json new file mode 100644 index 00000000..732bbe9a --- /dev/null +++ b/tests/fixtures/it_contact_list.json @@ -0,0 +1,16 @@ +{ + "object": "list", + "data": [ + { + "object": "it_contact", + "id": "it_contact_01HXYZ123456789ABCDEFGHIJ", + "email": "it-contact@example.com", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": "it_contact_01HXYZ123456789ABCDEFGHIJ", + "after": "it_contact_01HXYZ987654321KJIHGFEDCBA" + } +} diff --git a/tests/fixtures/it_contact_list_list_metadata.json b/tests/fixtures/it_contact_list_list_metadata.json new file mode 100644 index 00000000..598b6c02 --- /dev/null +++ b/tests/fixtures/it_contact_list_list_metadata.json @@ -0,0 +1,4 @@ +{ + "before": "it_contact_01HXYZ123456789ABCDEFGHIJ", + "after": "it_contact_01HXYZ987654321KJIHGFEDCBA" +} diff --git a/tests/fixtures/list_agent_blueprint.json b/tests/fixtures/list_agent_blueprint.json new file mode 100644 index 00000000..c2e5f08b --- /dev/null +++ b/tests/fixtures/list_agent_blueprint.json @@ -0,0 +1,33 @@ +{ + "data": [ + { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": [ + "crm:read", + "email:send" + ], + "invocable_by": { + "role_slugs": [ + "manager" + ], + "organization_ids": [ + "org_01EHWNCE74X7JSDV0X3SZ3KJNY" + ] + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600 + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/tests/fixtures/list_agent_instance.json b/tests/fixtures/list_agent_instance.json new file mode 100644 index 00000000..592bbbfa --- /dev/null +++ b/tests/fixtures/list_agent_instance.json @@ -0,0 +1,18 @@ +{ + "data": [ + { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/tests/fixtures/list_agent_instance_session.json b/tests/fixtures/list_agent_instance_session.json new file mode 100644 index 00000000..8366d68a --- /dev/null +++ b/tests/fixtures/list_agent_instance_session.json @@ -0,0 +1,18 @@ +{ + "data": [ + { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "status": "active", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": null, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/tests/fixtures/list_waitlist.json b/tests/fixtures/list_waitlist.json new file mode 100644 index 00000000..fe1e33af --- /dev/null +++ b/tests/fixtures/list_waitlist.json @@ -0,0 +1,14 @@ +{ + "data": [ + { + "object": "waitlist", + "id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/tests/fixtures/list_waitlist_entry.json b/tests/fixtures/list_waitlist_entry.json new file mode 100644 index 00000000..d124178f --- /dev/null +++ b/tests/fixtures/list_waitlist_entry.json @@ -0,0 +1,21 @@ +{ + "data": [ + { + "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", + "email": "marcelina.davis@example.com", + "state": "pending", + "approved_at": null, + "additional_fields": { + "company": "Example Corp" + }, + "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_entry" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/tests/fixtures/patch_connection.json b/tests/fixtures/patch_connection.json new file mode 100644 index 00000000..d202e3d9 --- /dev/null +++ b/tests/fixtures/patch_connection.json @@ -0,0 +1,35 @@ +{ + "name": "Foo Corp", + "external_id": "acme-legacy-conn-42", + "connection_type": "OktaSAML", + "attribute_maps": { + "standard_attributes": { + "idp_id": "sub", + "email": "email", + "first_name": "given_name", + "last_name": "family_name", + "groups": "memberOf", + "name": "displayName" + }, + "custom_attributes": { + "company": "company_claim" + } + }, + "saml_options": { + "idp_metadata_url": "https://idp.example.com/metadata.xml", + "acs_url": "https://example.auth0.com/login/callback?connection=123", + "sp_entity_id": "https://example.auth0.com/login/callback?connection=123", + "idp_entity_id": "https://idp.example.com/entity", + "idp_sso_url": "https://idp.example.com/sso" + }, + "oidc_options": { + "discovery_endpoint": "https://idp.example.com/.well-known/openid-configuration", + "client_id": "client_123", + "client_secret": "secret_xyz", + "redirect_uri": "https://auth.workos.com/sso/oidc/conn_externalkey/callback", + "pkce": true, + "token_authentication_method": "client_secret_basic", + "id_token_signature_algorithm": "RS256", + "fetch_user_info": false + } +} diff --git a/tests/fixtures/patch_connection_attribute_maps.json b/tests/fixtures/patch_connection_attribute_maps.json new file mode 100644 index 00000000..50dccfe6 --- /dev/null +++ b/tests/fixtures/patch_connection_attribute_maps.json @@ -0,0 +1,13 @@ +{ + "standard_attributes": { + "idp_id": "sub", + "email": "email", + "first_name": "given_name", + "last_name": "family_name", + "groups": "memberOf", + "name": "displayName" + }, + "custom_attributes": { + "company": "company_claim" + } +} diff --git a/tests/fixtures/patch_connection_oidc_options.json b/tests/fixtures/patch_connection_oidc_options.json new file mode 100644 index 00000000..f45be62f --- /dev/null +++ b/tests/fixtures/patch_connection_oidc_options.json @@ -0,0 +1,10 @@ +{ + "discovery_endpoint": "https://idp.example.com/.well-known/openid-configuration", + "client_id": "client_123", + "client_secret": "secret_xyz", + "redirect_uri": "https://auth.workos.com/sso/oidc/conn_externalkey/callback", + "pkce": true, + "token_authentication_method": "client_secret_basic", + "id_token_signature_algorithm": "RS256", + "fetch_user_info": false +} diff --git a/tests/fixtures/patch_connection_saml_options.json b/tests/fixtures/patch_connection_saml_options.json new file mode 100644 index 00000000..c8cc1e94 --- /dev/null +++ b/tests/fixtures/patch_connection_saml_options.json @@ -0,0 +1,7 @@ +{ + "idp_metadata_url": "https://idp.example.com/metadata.xml", + "acs_url": "https://example.auth0.com/login/callback?connection=123", + "sp_entity_id": "https://example.auth0.com/login/callback?connection=123", + "idp_entity_id": "https://idp.example.com/entity", + "idp_sso_url": "https://idp.example.com/sso" +} diff --git a/tests/fixtures/patch_connection_standard_attributes.json b/tests/fixtures/patch_connection_standard_attributes.json new file mode 100644 index 00000000..026736a9 --- /dev/null +++ b/tests/fixtures/patch_connection_standard_attributes.json @@ -0,0 +1,8 @@ +{ + "idp_id": "sub", + "email": "email", + "first_name": "given_name", + "last_name": "family_name", + "groups": "memberOf", + "name": "displayName" +} diff --git a/tests/fixtures/resource_export_completed.json b/tests/fixtures/resource_export_completed.json new file mode 100644 index 00000000..f4399d3e --- /dev/null +++ b/tests/fixtures/resource_export_completed.json @@ -0,0 +1,30 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.completed", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/resource_export_completed_data.json b/tests/fixtures/resource_export_completed_data.json new file mode 100644 index 00000000..860f54ba --- /dev/null +++ b/tests/fixtures/resource_export_completed_data.json @@ -0,0 +1,4 @@ +{ + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users" +} diff --git a/tests/fixtures/resource_export_created.json b/tests/fixtures/resource_export_created.json new file mode 100644 index 00000000..33a1eab3 --- /dev/null +++ b/tests/fixtures/resource_export_created.json @@ -0,0 +1,30 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.created", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/resource_export_created_data.json b/tests/fixtures/resource_export_created_data.json new file mode 100644 index 00000000..860f54ba --- /dev/null +++ b/tests/fixtures/resource_export_created_data.json @@ -0,0 +1,4 @@ +{ + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users" +} diff --git a/tests/fixtures/resource_export_downloaded.json b/tests/fixtures/resource_export_downloaded.json new file mode 100644 index 00000000..4dab7780 --- /dev/null +++ b/tests/fixtures/resource_export_downloaded.json @@ -0,0 +1,30 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.downloaded", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/resource_export_downloaded_data.json b/tests/fixtures/resource_export_downloaded_data.json new file mode 100644 index 00000000..860f54ba --- /dev/null +++ b/tests/fixtures/resource_export_downloaded_data.json @@ -0,0 +1,4 @@ +{ + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users" +} diff --git a/tests/fixtures/resource_export_failed.json b/tests/fixtures/resource_export_failed.json new file mode 100644 index 00000000..e1b370f7 --- /dev/null +++ b/tests/fixtures/resource_export_failed.json @@ -0,0 +1,30 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.failed", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/resource_export_failed_data.json b/tests/fixtures/resource_export_failed_data.json new file mode 100644 index 00000000..860f54ba --- /dev/null +++ b/tests/fixtures/resource_export_failed_data.json @@ -0,0 +1,4 @@ +{ + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users" +} diff --git a/tests/fixtures/saml_idp_signing_certificate.json b/tests/fixtures/saml_idp_signing_certificate.json new file mode 100644 index 00000000..86309440 --- /dev/null +++ b/tests/fixtures/saml_idp_signing_certificate.json @@ -0,0 +1,8 @@ +{ + "object": "saml_idp_signing_certificate", + "id": "saml_x509_cert_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": "2026-01-15T12:00:00.000Z", + "not_after": "2026-01-15T12:00:00.000Z", + "created_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/saml_idp_signing_certificate_list.json b/tests/fixtures/saml_idp_signing_certificate_list.json new file mode 100644 index 00000000..976a2c8e --- /dev/null +++ b/tests/fixtures/saml_idp_signing_certificate_list.json @@ -0,0 +1,13 @@ +{ + "object": "list", + "data": [ + { + "object": "saml_idp_signing_certificate", + "id": "saml_x509_cert_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": "2026-01-15T12:00:00.000Z", + "not_after": "2026-01-15T12:00:00.000Z", + "created_at": "2026-01-15T12:00:00.000Z" + } + ] +} diff --git a/tests/fixtures/saml_sp_encryption_certificate.json b/tests/fixtures/saml_sp_encryption_certificate.json new file mode 100644 index 00000000..9e7b49b5 --- /dev/null +++ b/tests/fixtures/saml_sp_encryption_certificate.json @@ -0,0 +1,8 @@ +{ + "object": "saml_sp_encryption_certificate", + "id": "saml_enc_key_pair_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": "2026-01-15T12:00:00.000Z", + "not_after": "2026-01-15T12:00:00.000Z", + "created_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/saml_sp_encryption_certificate_list.json b/tests/fixtures/saml_sp_encryption_certificate_list.json new file mode 100644 index 00000000..dda54238 --- /dev/null +++ b/tests/fixtures/saml_sp_encryption_certificate_list.json @@ -0,0 +1,13 @@ +{ + "object": "list", + "data": [ + { + "object": "saml_sp_encryption_certificate", + "id": "saml_enc_key_pair_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": "2026-01-15T12:00:00.000Z", + "not_after": "2026-01-15T12:00:00.000Z", + "created_at": "2026-01-15T12:00:00.000Z" + } + ] +} diff --git a/tests/fixtures/saml_sp_signing_certificate.json b/tests/fixtures/saml_sp_signing_certificate.json new file mode 100644 index 00000000..3574372d --- /dev/null +++ b/tests/fixtures/saml_sp_signing_certificate.json @@ -0,0 +1,8 @@ +{ + "object": "saml_sp_signing_certificate", + "id": "saml_party_trust_01E4ZCR3C56J083X43JQXF3JK5", + "value": "-----BEGIN CERTIFICATE-----MIIC...-----END CERTIFICATE-----", + "not_before": "2026-01-15T12:00:00.000Z", + "not_after": "2026-01-15T12:00:00.000Z", + "created_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/team.json b/tests/fixtures/team.json new file mode 100644 index 00000000..6b07b53b --- /dev/null +++ b/tests/fixtures/team.json @@ -0,0 +1,9 @@ +{ + "object": "team", + "id": "team_01JX9AN6E02HAG2Q2CKGC1XT5W", + "name": "Example Team", + "production_state": "Active", + "production_enabled_at": "2024-01-01T00:00:00.000Z", + "created_at": "2024-01-01T00:00:00.000Z", + "updated_at": "2024-01-01T00:00:00.000Z" +} diff --git a/tests/fixtures/token_query.json b/tests/fixtures/token_query.json index 1051d0d8..1476f807 100644 --- a/tests/fixtures/token_query.json +++ b/tests/fixtures/token_query.json @@ -2,5 +2,8 @@ "client_id": "client_01HZBC6N1EB1ZY7KG32X", "client_secret": "sk_example_123456789", "code": "authorization_code_value", - "grant_type": "authorization_code" + "grant_type": "authorization_code", + "subject_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMifQ.example", + "subject_token_type": "urn:ietf:params:oauth:token-type:id_token", + "organization_id": "org_01EHQMYV6MBK39QC5PZXHY59C3" } diff --git a/tests/fixtures/waitlist.json b/tests/fixtures/waitlist.json new file mode 100644 index 00000000..1eb2c771 --- /dev/null +++ b/tests/fixtures/waitlist.json @@ -0,0 +1,6 @@ +{ + "object": "waitlist", + "id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/waitlist_entry.json b/tests/fixtures/waitlist_entry.json new file mode 100644 index 00000000..96887183 --- /dev/null +++ b/tests/fixtures/waitlist_entry.json @@ -0,0 +1,13 @@ +{ + "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", + "email": "marcelina.davis@example.com", + "state": "pending", + "approved_at": null, + "additional_fields": { + "company": "Example Corp" + }, + "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_entry" +} diff --git a/tests/fixtures/waitlist_user.json b/tests/fixtures/waitlist_user.json index c4f74339..df5ee50e 100644 --- a/tests/fixtures/waitlist_user.json +++ b/tests/fixtures/waitlist_user.json @@ -1,10 +1,13 @@ { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": null, + "additional_fields": { + "company": "Example Corp" + }, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", - "updated_at": "2026-01-15T12:00:00.000Z" + "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user" } diff --git a/tests/fixtures/waitlist_user_approved.json b/tests/fixtures/waitlist_user_approved.json index 7019eba5..34466cb4 100644 --- a/tests/fixtures/waitlist_user_approved.json +++ b/tests/fixtures/waitlist_user_approved.json @@ -3,14 +3,17 @@ "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", "event": "waitlist_user.approved", "data": { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": null, + "additional_fields": { + "company": "Example Corp" + }, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", - "updated_at": "2026-01-15T12:00:00.000Z" + "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user" }, "created_at": "2026-01-15T12:00:00.000Z", "context": { diff --git a/tests/fixtures/waitlist_user_created.json b/tests/fixtures/waitlist_user_created.json index 1ba69bdd..b0f9b306 100644 --- a/tests/fixtures/waitlist_user_created.json +++ b/tests/fixtures/waitlist_user_created.json @@ -3,14 +3,17 @@ "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", "event": "waitlist_user.created", "data": { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": null, + "additional_fields": { + "company": "Example Corp" + }, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", - "updated_at": "2026-01-15T12:00:00.000Z" + "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user" }, "created_at": "2026-01-15T12:00:00.000Z", "context": { diff --git a/tests/fixtures/waitlist_user_denied.json b/tests/fixtures/waitlist_user_denied.json index 25e9b834..0306ad0e 100644 --- a/tests/fixtures/waitlist_user_denied.json +++ b/tests/fixtures/waitlist_user_denied.json @@ -3,14 +3,17 @@ "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", "event": "waitlist_user.denied", "data": { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": null, + "additional_fields": { + "company": "Example Corp" + }, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", - "updated_at": "2026-01-15T12:00:00.000Z" + "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user" }, "created_at": "2026-01-15T12:00:00.000Z", "context": { diff --git a/tests/test_agents.py b/tests/test_agents.py index 07edaa03..37d4cbe1 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -14,16 +14,128 @@ ServerError, UnprocessableEntityError, ) +from workos._pagination import AsyncPage, SyncPage from workos.agents.models import ( AgentAdminLinkClaimAttemptToExternalUserRequestUser, + AgentBlueprint, + AgentBlueprintsCreateRequestSessionSetting, AgentCredentialValidation, + AgentInstance, + AgentInstanceSession, AgentRegistration, + AgentToken, ClaimViewResponse, ) -from workos.common.models import AgentAdminValidateCredentialRequestType +from workos.common.models import ( + AgentAdminValidateCredentialRequestType, + AgentBlueprintsTokenMintTokenRequestType, + PaginationOrder, +) class TestAgents: + def test_list_blueprints(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("list_agent_blueprint.json"), + ) + page = workos.agents.list_blueprints() + assert isinstance(page, SyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], AgentBlueprint) + + def test_list_blueprints_empty_page(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = workos.agents.list_blueprints() + assert isinstance(page, SyncPage) + assert page.data == [] + + def test_list_blueprints_encodes_query_params(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.agents.list_blueprints( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + + def test_create_blueprint(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("agent_blueprint.json"), + ) + result = workos.agents.create_blueprint( + name="test_name", + session_settings=AgentBlueprintsCreateRequestSessionSetting.from_dict( + load_fixture("agent_blueprints_create_request_session_setting.json") + ), + ) + assert isinstance(result, AgentBlueprint) + assert result.object == "agent_blueprint" + assert result.id == "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/agents/blueprints") + body = json.loads(request.content) + assert body["name"] == "test_name" + assert "session_settings" in body + + def test_get_blueprint(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("agent_blueprint.json"), + ) + result = workos.agents.get_blueprint("test_agent_blueprint_id") + assert isinstance(result, AgentBlueprint) + assert result.object == "agent_blueprint" + assert result.id == "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/agents/blueprints/test_agent_blueprint_id") + + def test_update_blueprint(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("agent_blueprint.json"), + ) + result = workos.agents.update_blueprint("test_agent_blueprint_id") + assert isinstance(result, AgentBlueprint) + assert result.object == "agent_blueprint" + assert result.id == "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "PATCH" + assert request.url.path.endswith("/agents/blueprints/test_agent_blueprint_id") + + def test_delete_blueprint(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.agents.delete_blueprint("test_agent_blueprint_id") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/agents/blueprints/test_agent_blueprint_id") + + def test_create_blueprint_token(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("agent_token.json"), + ) + result = workos.agents.create_blueprint_token( + "test_agent_blueprint_id", + type=AgentBlueprintsTokenMintTokenRequestType("user_delegated"), + ) + assert isinstance(result, AgentToken) + assert result.access_token == "eyJhbGciOiJSUzI1NiIsImtpZCI6..." + assert result.token_type == "Bearer" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/agents/blueprints/test_agent_blueprint_id/tokens" + ) + body = json.loads(request.content) + assert body["type"] == AgentBlueprintsTokenMintTokenRequestType( + "user_delegated" + ) + def test_update_attempts(self, workos, httpx_mock): httpx_mock.add_response( json=load_fixture("claim_view_response.json"), @@ -78,57 +190,152 @@ def test_get_registration(self, workos, httpx_mock): assert request.method == "GET" assert request.url.path.endswith("/agents/registrations/test_id") - def test_update_attempts_with_request_options(self, workos, httpx_mock): - httpx_mock.add_response(json=load_fixture("claim_view_response.json")) - workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - request_options={"extra_headers": {"X-Custom": "value"}}, + def test_list_instances(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("list_agent_instance.json"), + ) + page = workos.agents.list_instances() + assert isinstance(page, SyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], AgentInstance) + + def test_list_instances_empty_page(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = workos.agents.list_instances() + assert isinstance(page, SyncPage) + assert page.data == [] + + def test_list_instances_encodes_query_params(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.agents.list_instances( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + organization_id="value organization_id/test", + agent_blueprint_id="value agent_blueprint_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + assert request.url.params["organization_id"] == "value organization_id/test" + assert ( + request.url.params["agent_blueprint_id"] == "value agent_blueprint_id/test" + ) + + def test_get_instance(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("agent_instance.json"), + ) + result = workos.agents.get_instance("test_agent_instance_id") + assert isinstance(result, AgentInstance) + assert result.object == "agent_instance" + assert result.id == "agent_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/agents/instances/test_agent_instance_id") + + def test_delete_instance(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.agents.delete_instance("test_agent_instance_id") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/agents/instances/test_agent_instance_id") + + def test_list_sessions(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("list_agent_instance_session.json"), + ) + page = workos.agents.list_sessions() + assert isinstance(page, SyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], AgentInstanceSession) + + def test_list_sessions_empty_page(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = workos.agents.list_sessions() + assert isinstance(page, SyncPage) + assert page.data == [] + + def test_list_sessions_encodes_query_params(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.agents.list_sessions( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + agent_blueprint_id="value agent_blueprint_id/test", + agent_instance_id="value agent_instance_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + assert ( + request.url.params["agent_blueprint_id"] == "value agent_blueprint_id/test" + ) + assert request.url.params["agent_instance_id"] == "value agent_instance_id/test" + + def test_get_session(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("agent_instance_session.json"), + ) + result = workos.agents.get_session("test_agent_instance_session_id") + assert isinstance(result, AgentInstanceSession) + assert result.object == "agent_instance_session" + assert result.id == "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/agents/sessions/test_agent_instance_session_id" + ) + + def test_revoke_session(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("agent_instance_session.json"), + ) + result = workos.agents.revoke_session("test_agent_instance_session_id") + assert isinstance(result, AgentInstanceSession) + assert result.object == "agent_instance_session" + assert result.id == "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/agents/sessions/test_agent_instance_session_id/revoke" + ) + + def test_list_blueprints_with_request_options(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.agents.list_blueprints( + request_options={"extra_headers": {"X-Custom": "value"}} ) request = httpx_mock.get_request() assert request.headers["X-Custom"] == "value" - def test_update_attempts_unauthorized(self, workos, httpx_mock): + def test_list_blueprints_unauthorized(self, workos, httpx_mock): httpx_mock.add_response( status_code=401, json={"message": "Unauthorized"}, ) with pytest.raises(AuthenticationError): - workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + workos.agents.list_blueprints() - def test_update_attempts_not_found(self, httpx_mock): + def test_list_blueprints_not_found(self, httpx_mock): workos = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) try: httpx_mock.add_response(status_code=404, json={"message": "Not found"}) with pytest.raises(NotFoundError): - workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + workos.agents.list_blueprints() finally: workos.close() - def test_update_attempts_rate_limited(self, httpx_mock): + def test_list_blueprints_rate_limited(self, httpx_mock): workos = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) @@ -139,77 +346,138 @@ def test_update_attempts_rate_limited(self, httpx_mock): json={"message": "Slow down"}, ) with pytest.raises(RateLimitExceededError): - workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + workos.agents.list_blueprints() finally: workos.close() - def test_update_attempts_server_error(self, httpx_mock): + def test_list_blueprints_server_error(self, httpx_mock): workos = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) try: httpx_mock.add_response(status_code=500, json={"message": "Server error"}) with pytest.raises(ServerError): - workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + workos.agents.list_blueprints() finally: workos.close() - def test_update_attempts_bad_request(self, httpx_mock): + def test_list_blueprints_bad_request(self, httpx_mock): workos = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) try: httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) with pytest.raises(BadRequestError): - workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + workos.agents.list_blueprints() finally: workos.close() - def test_update_attempts_unprocessable(self, httpx_mock): + def test_list_blueprints_unprocessable(self, httpx_mock): workos = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) try: httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) with pytest.raises(UnprocessableEntityError): - workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + workos.agents.list_blueprints() finally: workos.close() class TestAsyncAgents: + @pytest.mark.asyncio + async def test_list_blueprints(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("list_agent_blueprint.json")) + page = await async_workos.agents.list_blueprints() + assert isinstance(page, AsyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], AgentBlueprint) + + @pytest.mark.asyncio + async def test_list_blueprints_empty_page(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = await async_workos.agents.list_blueprints() + assert isinstance(page, AsyncPage) + assert page.data == [] + + @pytest.mark.asyncio + async def test_list_blueprints_encodes_query_params(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.agents.list_blueprints( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + + @pytest.mark.asyncio + async def test_create_blueprint(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("agent_blueprint.json")) + result = await async_workos.agents.create_blueprint( + name="test_name", + session_settings=AgentBlueprintsCreateRequestSessionSetting.from_dict( + load_fixture("agent_blueprints_create_request_session_setting.json") + ), + ) + assert isinstance(result, AgentBlueprint) + assert result.object == "agent_blueprint" + assert result.id == "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/agents/blueprints") + + @pytest.mark.asyncio + async def test_get_blueprint(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("agent_blueprint.json")) + result = await async_workos.agents.get_blueprint("test_agent_blueprint_id") + assert isinstance(result, AgentBlueprint) + assert result.object == "agent_blueprint" + assert result.id == "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/agents/blueprints/test_agent_blueprint_id") + + @pytest.mark.asyncio + async def test_update_blueprint(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("agent_blueprint.json")) + result = await async_workos.agents.update_blueprint("test_agent_blueprint_id") + assert isinstance(result, AgentBlueprint) + assert result.object == "agent_blueprint" + assert result.id == "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "PATCH" + assert request.url.path.endswith("/agents/blueprints/test_agent_blueprint_id") + + @pytest.mark.asyncio + async def test_delete_blueprint(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = await async_workos.agents.delete_blueprint("test_agent_blueprint_id") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/agents/blueprints/test_agent_blueprint_id") + + @pytest.mark.asyncio + async def test_create_blueprint_token(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("agent_token.json")) + result = await async_workos.agents.create_blueprint_token( + "test_agent_blueprint_id", + type=AgentBlueprintsTokenMintTokenRequestType("user_delegated"), + ) + assert isinstance(result, AgentToken) + assert result.access_token == "eyJhbGciOiJSUzI1NiIsImtpZCI6..." + assert result.token_type == "Bearer" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/agents/blueprints/test_agent_blueprint_id/tokens" + ) + @pytest.mark.asyncio async def test_update_attempts(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("claim_view_response.json")) @@ -255,57 +523,154 @@ async def test_get_registration(self, async_workos, httpx_mock): assert request.url.path.endswith("/agents/registrations/test_id") @pytest.mark.asyncio - async def test_update_attempts_with_request_options(self, async_workos, httpx_mock): - httpx_mock.add_response(json=load_fixture("claim_view_response.json")) - await async_workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - request_options={"extra_headers": {"X-Custom": "value"}}, + async def test_list_instances(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("list_agent_instance.json")) + page = await async_workos.agents.list_instances() + assert isinstance(page, AsyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], AgentInstance) + + @pytest.mark.asyncio + async def test_list_instances_empty_page(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = await async_workos.agents.list_instances() + assert isinstance(page, AsyncPage) + assert page.data == [] + + @pytest.mark.asyncio + async def test_list_instances_encodes_query_params(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.agents.list_instances( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + organization_id="value organization_id/test", + agent_blueprint_id="value agent_blueprint_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + assert request.url.params["organization_id"] == "value organization_id/test" + assert ( + request.url.params["agent_blueprint_id"] == "value agent_blueprint_id/test" + ) + + @pytest.mark.asyncio + async def test_get_instance(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("agent_instance.json")) + result = await async_workos.agents.get_instance("test_agent_instance_id") + assert isinstance(result, AgentInstance) + assert result.object == "agent_instance" + assert result.id == "agent_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/agents/instances/test_agent_instance_id") + + @pytest.mark.asyncio + async def test_delete_instance(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = await async_workos.agents.delete_instance("test_agent_instance_id") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/agents/instances/test_agent_instance_id") + + @pytest.mark.asyncio + async def test_list_sessions(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("list_agent_instance_session.json")) + page = await async_workos.agents.list_sessions() + assert isinstance(page, AsyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], AgentInstanceSession) + + @pytest.mark.asyncio + async def test_list_sessions_empty_page(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = await async_workos.agents.list_sessions() + assert isinstance(page, AsyncPage) + assert page.data == [] + + @pytest.mark.asyncio + async def test_list_sessions_encodes_query_params(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.agents.list_sessions( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + agent_blueprint_id="value agent_blueprint_id/test", + agent_instance_id="value agent_instance_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + assert ( + request.url.params["agent_blueprint_id"] == "value agent_blueprint_id/test" + ) + assert request.url.params["agent_instance_id"] == "value agent_instance_id/test" + + @pytest.mark.asyncio + async def test_get_session(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("agent_instance_session.json")) + result = await async_workos.agents.get_session("test_agent_instance_session_id") + assert isinstance(result, AgentInstanceSession) + assert result.object == "agent_instance_session" + assert result.id == "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/agents/sessions/test_agent_instance_session_id" + ) + + @pytest.mark.asyncio + async def test_revoke_session(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("agent_instance_session.json")) + result = await async_workos.agents.revoke_session( + "test_agent_instance_session_id" + ) + assert isinstance(result, AgentInstanceSession) + assert result.object == "agent_instance_session" + assert result.id == "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/agents/sessions/test_agent_instance_session_id/revoke" + ) + + @pytest.mark.asyncio + async def test_list_blueprints_with_request_options(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.agents.list_blueprints( + request_options={"extra_headers": {"X-Custom": "value"}} ) request = httpx_mock.get_request() assert request.headers["X-Custom"] == "value" @pytest.mark.asyncio - async def test_update_attempts_unauthorized(self, async_workos, httpx_mock): + async def test_list_blueprints_unauthorized(self, async_workos, httpx_mock): httpx_mock.add_response(status_code=401, json={"message": "Unauthorized"}) with pytest.raises(AuthenticationError): - await async_workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + await async_workos.agents.list_blueprints() @pytest.mark.asyncio - async def test_update_attempts_not_found(self, httpx_mock): + async def test_list_blueprints_not_found(self, httpx_mock): workos = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) try: httpx_mock.add_response(status_code=404, json={"message": "Not found"}) with pytest.raises(NotFoundError): - await workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + await workos.agents.list_blueprints() finally: await workos.close() @pytest.mark.asyncio - async def test_update_attempts_rate_limited(self, httpx_mock): + async def test_list_blueprints_rate_limited(self, httpx_mock): workos = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) @@ -316,74 +681,42 @@ async def test_update_attempts_rate_limited(self, httpx_mock): json={"message": "Slow down"}, ) with pytest.raises(RateLimitExceededError): - await workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + await workos.agents.list_blueprints() finally: await workos.close() @pytest.mark.asyncio - async def test_update_attempts_server_error(self, httpx_mock): + async def test_list_blueprints_server_error(self, httpx_mock): workos = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) try: httpx_mock.add_response(status_code=500, json={"message": "Server error"}) with pytest.raises(ServerError): - await workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + await workos.agents.list_blueprints() finally: await workos.close() @pytest.mark.asyncio - async def test_update_attempts_bad_request(self, httpx_mock): + async def test_list_blueprints_bad_request(self, httpx_mock): workos = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) try: httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) with pytest.raises(BadRequestError): - await workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + await workos.agents.list_blueprints() finally: await workos.close() @pytest.mark.asyncio - async def test_update_attempts_unprocessable(self, httpx_mock): + async def test_list_blueprints_unprocessable(self, httpx_mock): workos = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) try: httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) with pytest.raises(UnprocessableEntityError): - await workos.agents.update_attempts( - type="link_external_user", - claim_attempt_token="test_claim_attempt_token", - user=AgentAdminLinkClaimAttemptToExternalUserRequestUser.from_dict( - load_fixture( - "agent_admin_link_claim_attempt_to_external_user_request_user.json" - ) - ), - ) + await workos.agents.list_blueprints() finally: await workos.close() diff --git a/tests/test_agents_models_round_trip.py b/tests/test_agents_models_round_trip.py index f8c87062..80954241 100644 --- a/tests/test_agents_models_round_trip.py +++ b/tests/test_agents_models_round_trip.py @@ -5,11 +5,21 @@ from tests.generated_helpers import load_fixture from workos.agents.models import ( AgentAdminLinkClaimAttemptToExternalUserRequestUser, + AgentBlueprint, + AgentBlueprintInvocableBy, + AgentBlueprintsCreateRequestInvocableBy, + AgentBlueprintsCreateRequestSessionSetting, + AgentBlueprintSessionSetting, + AgentBlueprintsUpdateRequestInvocableBy, + AgentBlueprintsUpdateRequestSessionSetting, AgentCredentialValidation, + AgentInstance, + AgentInstanceSession, AgentRegistration, AgentRegistrationAgentIdentity, AgentRegistrationClaim, AgentRegistrationClaimClaimCompletion, + AgentToken, ClaimViewResponse, ClaimViewResponseOrganization, ) @@ -161,6 +171,263 @@ def test_claim_view_response_round_trips_unknown_enum_values(self): instance = ClaimViewResponse.from_dict(data) assert instance.to_dict() == data + def test_agent_blueprint_round_trip(self): + data = load_fixture("agent_blueprint.json") + instance = AgentBlueprint.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprint.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_minimal_payload(self): + data = { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": None, + "permissions": ["crm:read", "email:send"], + "invocable_by": { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprint.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["name"] == data["name"] + assert serialized["description"] == data["description"] + assert serialized["permissions"] == data["permissions"] + assert serialized["invocable_by"] == data["invocable_by"] + assert serialized["session_settings"] == data["session_settings"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_agent_blueprint_preserves_nullable_fields(self): + data = { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": None, + "permissions": ["crm:read", "email:send"], + "invocable_by": { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprint.from_dict(data) + serialized = instance.to_dict() + assert serialized["description"] is None + + def test_agent_instance_round_trip(self): + data = load_fixture("agent_instance.json") + instance = AgentInstance.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentInstance.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_instance_minimal_payload(self): + data = { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": None, + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstance.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["agent_blueprint_id"] == data["agent_blueprint_id"] + assert serialized["organization_id"] == data["organization_id"] + assert ( + serialized["organization_membership_id"] + == data["organization_membership_id"] + ) + assert serialized["type"] == data["type"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_agent_instance_preserves_nullable_fields(self): + data = { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": None, + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstance.from_dict(data) + serialized = instance.to_dict() + assert serialized["organization_membership_id"] is None + + def test_agent_instance_round_trips_unknown_enum_values(self): + data = { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "unexpected_agent_instance_type", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstance.from_dict(data) + assert instance.to_dict() == data + + def test_agent_token_round_trip(self): + data = load_fixture("agent_token.json") + instance = AgentToken.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentToken.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_token_minimal_payload(self): + data = { + "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...", + "token_type": "Bearer", + "expires_in": 300, + "refresh_token": "njGkA8Wyht0GBEGGA0Zh1Q3wZzL2...", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "new_instance": False, + "agent_instance_session_id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "permissions": ["crm:read"], + } + instance = AgentToken.from_dict(data) + serialized = instance.to_dict() + assert serialized["access_token"] == data["access_token"] + assert serialized["token_type"] == data["token_type"] + assert serialized["expires_in"] == data["expires_in"] + assert serialized["refresh_token"] == data["refresh_token"] + assert serialized["agent_instance_id"] == data["agent_instance_id"] + assert serialized["new_instance"] == data["new_instance"] + assert ( + serialized["agent_instance_session_id"] == data["agent_instance_session_id"] + ) + assert serialized["permissions"] == data["permissions"] + + def test_agent_instance_session_round_trip(self): + data = load_fixture("agent_instance_session.json") + instance = AgentInstanceSession.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentInstanceSession.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_instance_session_minimal_payload(self): + data = { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "status": "active", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceSession.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["agent_instance_id"] == data["agent_instance_id"] + assert serialized["status"] == data["status"] + assert serialized["expires_at"] == data["expires_at"] + assert serialized["revoked_at"] == data["revoked_at"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_agent_instance_session_preserves_nullable_fields(self): + data = { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "status": "active", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceSession.from_dict(data) + serialized = instance.to_dict() + assert serialized["revoked_at"] is None + + def test_agent_instance_session_round_trips_unknown_enum_values(self): + data = { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "status": "unexpected_agent_instance_session_status", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceSession.from_dict(data) + assert instance.to_dict() == data + + def test_agent_blueprint_invocable_by_round_trip(self): + data = load_fixture("agent_blueprint_invocable_by.json") + instance = AgentBlueprintInvocableBy.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintInvocableBy.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_invocable_by_minimal_payload(self): + data = { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + } + instance = AgentBlueprintInvocableBy.from_dict(data) + serialized = instance.to_dict() + assert serialized["role_slugs"] == data["role_slugs"] + assert serialized["organization_ids"] == data["organization_ids"] + + def test_agent_blueprint_session_setting_round_trip(self): + data = load_fixture("agent_blueprint_session_setting.json") + instance = AgentBlueprintSessionSetting.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintSessionSetting.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_session_setting_minimal_payload(self): + data = { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + } + instance = AgentBlueprintSessionSetting.from_dict(data) + serialized = instance.to_dict() + assert serialized["max_age_seconds"] == data["max_age_seconds"] + assert ( + serialized["access_token_ttl_seconds"] == data["access_token_ttl_seconds"] + ) + assert ( + serialized["refresh_token_ttl_seconds"] == data["refresh_token_ttl_seconds"] + ) + def test_claim_view_response_organization_round_trip(self): data = load_fixture("claim_view_response_organization.json") instance = ClaimViewResponseOrganization.from_dict(data) @@ -245,6 +512,97 @@ def test_agent_registration_claim_preserves_nullable_fields(self): serialized = instance.to_dict() assert serialized["claim_completion"] is None + def test_agent_blueprints_create_request_invocable_by_round_trip(self): + data = load_fixture("agent_blueprints_create_request_invocable_by.json") + instance = AgentBlueprintsCreateRequestInvocableBy.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintsCreateRequestInvocableBy.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprints_create_request_invocable_by_minimal_payload(self): + data = {} + instance = AgentBlueprintsCreateRequestInvocableBy.from_dict(data) + assert instance.to_dict() is not None + + def test_agent_blueprints_create_request_invocable_by_omits_absent_optional_non_nullable_fields( + self, + ): + data = {} + instance = AgentBlueprintsCreateRequestInvocableBy.from_dict(data) + serialized = instance.to_dict() + assert "role_slugs" not in serialized + assert "organization_ids" not in serialized + + def test_agent_blueprints_create_request_session_setting_round_trip(self): + data = load_fixture("agent_blueprints_create_request_session_setting.json") + instance = AgentBlueprintsCreateRequestSessionSetting.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintsCreateRequestSessionSetting.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprints_create_request_session_setting_minimal_payload(self): + data = { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + } + instance = AgentBlueprintsCreateRequestSessionSetting.from_dict(data) + serialized = instance.to_dict() + assert serialized["max_age_seconds"] == data["max_age_seconds"] + assert ( + serialized["access_token_ttl_seconds"] == data["access_token_ttl_seconds"] + ) + assert ( + serialized["refresh_token_ttl_seconds"] == data["refresh_token_ttl_seconds"] + ) + + def test_agent_blueprints_update_request_invocable_by_round_trip(self): + data = load_fixture("agent_blueprints_update_request_invocable_by.json") + instance = AgentBlueprintsUpdateRequestInvocableBy.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintsUpdateRequestInvocableBy.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprints_update_request_invocable_by_minimal_payload(self): + data = {} + instance = AgentBlueprintsUpdateRequestInvocableBy.from_dict(data) + assert instance.to_dict() is not None + + def test_agent_blueprints_update_request_invocable_by_omits_absent_optional_non_nullable_fields( + self, + ): + data = {} + instance = AgentBlueprintsUpdateRequestInvocableBy.from_dict(data) + serialized = instance.to_dict() + assert "role_slugs" not in serialized + assert "organization_ids" not in serialized + + def test_agent_blueprints_update_request_session_setting_round_trip(self): + data = load_fixture("agent_blueprints_update_request_session_setting.json") + instance = AgentBlueprintsUpdateRequestSessionSetting.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintsUpdateRequestSessionSetting.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprints_update_request_session_setting_minimal_payload(self): + data = {} + instance = AgentBlueprintsUpdateRequestSessionSetting.from_dict(data) + assert instance.to_dict() is not None + + def test_agent_blueprints_update_request_session_setting_omits_absent_optional_non_nullable_fields( + self, + ): + data = {} + instance = AgentBlueprintsUpdateRequestSessionSetting.from_dict(data) + serialized = instance.to_dict() + assert "max_age_seconds" not in serialized + assert "access_token_ttl_seconds" not in serialized + assert "refresh_token_ttl_seconds" not in serialized + def test_agent_admin_link_claim_attempt_to_external_user_request_user_round_trip( self, ): diff --git a/tests/test_authorization.py b/tests/test_authorization.py index b546bf77..8361df74 100644 --- a/tests/test_authorization.py +++ b/tests/test_authorization.py @@ -151,7 +151,7 @@ def test_check(self, workos, httpx_mock): result = workos.authorization.check( "test_organization_membership_id", permission_slug="test_permission_slug", - resource_target=ResourceTargetById(resource_id="test_value"), + resource_target=ResourceTargetById(resource_id="test_resource_id"), ) assert isinstance(result, AuthorizationCheck) assert result.authorized is True @@ -169,7 +169,9 @@ def test_list_resources_for_membership(self, workos, httpx_mock): ) page = workos.authorization.list_resources_for_membership( "test_organization_membership_id", - parent_resource=ParentResourceById(parent_resource_id="test_value"), + parent_resource=ParentResourceById( + parent_resource_id="test_parent_resource_id" + ), permission_slug="test_permission_slug", ) assert isinstance(page, SyncPage) @@ -180,7 +182,9 @@ def test_list_resources_for_membership_empty_page(self, workos, httpx_mock): httpx_mock.add_response(json={"data": [], "list_metadata": {}}) page = workos.authorization.list_resources_for_membership( "test_organization_membership_id", - parent_resource=ParentResourceById(parent_resource_id="test_value"), + parent_resource=ParentResourceById( + parent_resource_id="test_parent_resource_id" + ), permission_slug="test_permission_slug", ) assert isinstance(page, SyncPage) @@ -342,7 +346,7 @@ def test_assign_role(self, workos, httpx_mock): result = workos.authorization.assign_role( "test_organization_membership_id", role_slug="test_role_slug", - resource_target=ResourceTargetById(resource_id="test_value"), + resource_target=ResourceTargetById(resource_id="test_resource_id"), ) assert isinstance(result, UserRoleAssignment) assert result.object == "role_assignment" @@ -360,7 +364,7 @@ def test_remove_role(self, workos, httpx_mock): result = workos.authorization.remove_role( "test_organization_membership_id", role_slug="test_role_slug", - resource_target=ResourceTargetById(resource_id="test_value"), + resource_target=ResourceTargetById(resource_id="test_resource_id"), ) assert result is None request = httpx_mock.get_request() @@ -528,7 +532,9 @@ def test_update_resource_by_external_id(self, workos, httpx_mock): "test_organization_id", "test_resource_type_slug", "test_external_id", - parent_resource=ParentResourceById(parent_resource_id="test_value"), + parent_resource=ParentResourceById( + parent_resource_id="test_parent_resource_id" + ), ) assert isinstance(result, AuthorizationResource) assert result.object == "authorization_resource" @@ -663,7 +669,7 @@ def test_list_resources(self, workos, httpx_mock): json=load_fixture("list_authorization_resource.json"), ) page = workos.authorization.list_resources( - parent=ParentById(parent_resource_id="test_value") + parent=ParentById(parent_resource_id="test_parent_resource_id") ) assert isinstance(page, SyncPage) assert len(page.data) == 1 @@ -672,7 +678,7 @@ def test_list_resources(self, workos, httpx_mock): def test_list_resources_empty_page(self, workos, httpx_mock): httpx_mock.add_response(json={"data": [], "list_metadata": {}}) page = workos.authorization.list_resources( - parent=ParentById(parent_resource_id="test_value") + parent=ParentById(parent_resource_id="test_parent_resource_id") ) assert isinstance(page, SyncPage) assert page.data == [] @@ -715,7 +721,9 @@ def test_create_resource(self, workos, httpx_mock): name="test_name", resource_type_slug="test_resource_type_slug", organization_id="test_organization_id", - parent_resource=ParentResourceById(parent_resource_id="test_value"), + parent_resource=ParentResourceById( + parent_resource_id="test_parent_resource_id" + ), ) assert isinstance(result, AuthorizationResource) assert result.object == "authorization_resource" @@ -747,7 +755,9 @@ def test_update_resource(self, workos, httpx_mock): ) result = workos.authorization.update_resource( "test_resource_id", - parent_resource=ParentResourceById(parent_resource_id="test_value"), + parent_resource=ParentResourceById( + parent_resource_id="test_parent_resource_id" + ), ) assert isinstance(result, AuthorizationResource) assert result.object == "authorization_resource" @@ -1202,7 +1212,7 @@ async def test_check(self, async_workos, httpx_mock): result = await async_workos.authorization.check( "test_organization_membership_id", permission_slug="test_permission_slug", - resource_target=ResourceTargetById(resource_id="test_value"), + resource_target=ResourceTargetById(resource_id="test_resource_id"), ) assert isinstance(result, AuthorizationCheck) assert result.authorized is True @@ -1217,7 +1227,9 @@ async def test_list_resources_for_membership(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("list_authorization_resource.json")) page = await async_workos.authorization.list_resources_for_membership( "test_organization_membership_id", - parent_resource=ParentResourceById(parent_resource_id="test_value"), + parent_resource=ParentResourceById( + parent_resource_id="test_parent_resource_id" + ), permission_slug="test_permission_slug", ) assert isinstance(page, AsyncPage) @@ -1231,7 +1243,9 @@ async def test_list_resources_for_membership_empty_page( httpx_mock.add_response(json={"data": [], "list_metadata": {}}) page = await async_workos.authorization.list_resources_for_membership( "test_organization_membership_id", - parent_resource=ParentResourceById(parent_resource_id="test_value"), + parent_resource=ParentResourceById( + parent_resource_id="test_parent_resource_id" + ), permission_slug="test_permission_slug", ) assert isinstance(page, AsyncPage) @@ -1408,7 +1422,7 @@ async def test_assign_role(self, async_workos, httpx_mock): result = await async_workos.authorization.assign_role( "test_organization_membership_id", role_slug="test_role_slug", - resource_target=ResourceTargetById(resource_id="test_value"), + resource_target=ResourceTargetById(resource_id="test_resource_id"), ) assert isinstance(result, UserRoleAssignment) assert result.object == "role_assignment" @@ -1425,7 +1439,7 @@ async def test_remove_role(self, async_workos, httpx_mock): result = await async_workos.authorization.remove_role( "test_organization_membership_id", role_slug="test_role_slug", - resource_target=ResourceTargetById(resource_id="test_value"), + resource_target=ResourceTargetById(resource_id="test_resource_id"), ) assert result is None request = httpx_mock.get_request() @@ -1584,7 +1598,9 @@ async def test_update_resource_by_external_id(self, async_workos, httpx_mock): "test_organization_id", "test_resource_type_slug", "test_external_id", - parent_resource=ParentResourceById(parent_resource_id="test_value"), + parent_resource=ParentResourceById( + parent_resource_id="test_parent_resource_id" + ), ) assert isinstance(result, AuthorizationResource) assert result.object == "authorization_resource" @@ -1726,7 +1742,7 @@ async def test_list_role_assignments_for_resource_by_external_id_encodes_query_p async def test_list_resources(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("list_authorization_resource.json")) page = await async_workos.authorization.list_resources( - parent=ParentById(parent_resource_id="test_value") + parent=ParentById(parent_resource_id="test_parent_resource_id") ) assert isinstance(page, AsyncPage) assert len(page.data) == 1 @@ -1736,7 +1752,7 @@ async def test_list_resources(self, async_workos, httpx_mock): async def test_list_resources_empty_page(self, async_workos, httpx_mock): httpx_mock.add_response(json={"data": [], "list_metadata": {}}) page = await async_workos.authorization.list_resources( - parent=ParentById(parent_resource_id="test_value") + parent=ParentById(parent_resource_id="test_parent_resource_id") ) assert isinstance(page, AsyncPage) assert page.data == [] @@ -1779,7 +1795,9 @@ async def test_create_resource(self, async_workos, httpx_mock): name="test_name", resource_type_slug="test_resource_type_slug", organization_id="test_organization_id", - parent_resource=ParentResourceById(parent_resource_id="test_value"), + parent_resource=ParentResourceById( + parent_resource_id="test_parent_resource_id" + ), ) assert isinstance(result, AuthorizationResource) assert result.object == "authorization_resource" @@ -1804,7 +1822,9 @@ async def test_update_resource(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("authorization_resource.json")) result = await async_workos.authorization.update_resource( "test_resource_id", - parent_resource=ParentResourceById(parent_resource_id="test_value"), + parent_resource=ParentResourceById( + parent_resource_id="test_parent_resource_id" + ), ) assert isinstance(result, AuthorizationResource) assert result.object == "authorization_resource" diff --git a/tests/test_common_models_round_trip.py b/tests/test_common_models_round_trip.py index 28cb003e..c9476f4e 100644 --- a/tests/test_common_models_round_trip.py +++ b/tests/test_common_models_round_trip.py @@ -9,6 +9,24 @@ ActionAuthenticationDeniedData, ActionUserRegistrationDenied, ActionUserRegistrationDeniedData, + AgentBlueprintCreated, + AgentBlueprintCreatedData, + AgentBlueprintCreatedDataInvocableBy, + AgentBlueprintCreatedDataSessionSetting, + AgentBlueprintDeleted, + AgentBlueprintDeletedData, + AgentBlueprintUpdated, + AgentBlueprintUpdatedData, + AgentBlueprintUpdatedDataInvocableBy, + AgentBlueprintUpdatedDataSessionSetting, + AgentInstanceCreated, + AgentInstanceCreatedData, + AgentInstanceDeleted, + AgentInstanceDeletedData, + AgentInstanceSessionCreated, + AgentInstanceSessionCreatedData, + AgentInstanceSessionRevoked, + AgentInstanceSessionRevokedData, AgentRegistrationClaimAttemptCreated, AgentRegistrationClaimAttemptCreatedData, AgentRegistrationClaimCompleted, @@ -236,6 +254,14 @@ PipesConnectedAccountReauthorizationNeeded, RadarChallengeCreated, RadarChallengeCreatedData, + ResourceExportCompleted, + ResourceExportCompletedData, + ResourceExportCreated, + ResourceExportCreatedData, + ResourceExportDownloaded, + ResourceExportDownloadedData, + ResourceExportFailed, + ResourceExportFailedData, RoleCreated, RoleCreatedData, RoleDeleted, @@ -719,34 +745,50 @@ def test_waitlist_user_round_trip(self): def test_waitlist_user_minimal_payload(self): data = { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": None, "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user", } instance = WaitlistUser.from_dict(data) serialized = instance.to_dict() - assert serialized["object"] == data["object"] assert serialized["id"] == data["id"] assert serialized["email"] == data["email"] assert serialized["state"] == data["state"] assert serialized["approved_at"] == data["approved_at"] assert serialized["created_at"] == data["created_at"] assert serialized["updated_at"] == data["updated_at"] + assert serialized["object"] == data["object"] - def test_waitlist_user_preserves_nullable_fields(self): + def test_waitlist_user_omits_absent_optional_non_nullable_fields(self): data = { + "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", + "email": "marcelina.davis@example.com", + "state": "pending", + "approved_at": None, + "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", "object": "waitlist_user", + } + instance = WaitlistUser.from_dict(data) + serialized = instance.to_dict() + assert "additional_fields" not in serialized + + def test_waitlist_user_preserves_nullable_fields(self): + data = { "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": None, + "additional_fields": {"company": "Example Corp"}, "waitlist_id": None, "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user", } instance = WaitlistUser.from_dict(data) serialized = instance.to_dict() @@ -755,14 +797,15 @@ def test_waitlist_user_preserves_nullable_fields(self): def test_waitlist_user_round_trips_unknown_enum_values(self): data = { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "unexpected_waitlist_user_state", "approved_at": None, + "additional_fields": {"company": "Example Corp"}, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user", } instance = WaitlistUser.from_dict(data) assert instance.to_dict() == data @@ -927,56 +970,915 @@ def test_action_user_registration_denied_omits_absent_optional_non_nullable_fiel }, "created_at": "2026-01-15T12:00:00.000Z", } - instance = ActionUserRegistrationDenied.from_dict(data) + instance = ActionUserRegistrationDenied.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_action_user_registration_denied_data_round_trip(self): + data = load_fixture("action_user_registration_denied_data.json") + instance = ActionUserRegistrationDeniedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ActionUserRegistrationDeniedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_action_user_registration_denied_data_minimal_payload(self): + data = { + "action_endpoint_id": "action_endpoint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "action_execution_id": "action_execution_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "user_registration", + "verdict": "Deny", + "organization_id": None, + "email": "user@example.com", + "ip_address": None, + "user_agent": None, + } + instance = ActionUserRegistrationDeniedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["action_endpoint_id"] == data["action_endpoint_id"] + assert serialized["action_execution_id"] == data["action_execution_id"] + assert serialized["type"] == data["type"] + assert serialized["verdict"] == data["verdict"] + assert serialized["organization_id"] == data["organization_id"] + assert serialized["email"] == data["email"] + assert serialized["ip_address"] == data["ip_address"] + assert serialized["user_agent"] == data["user_agent"] + + def test_action_user_registration_denied_data_preserves_nullable_fields(self): + data = { + "action_endpoint_id": "action_endpoint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "action_execution_id": "action_execution_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "user_registration", + "verdict": "Deny", + "organization_id": None, + "email": "user@example.com", + "ip_address": None, + "user_agent": None, + } + instance = ActionUserRegistrationDeniedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["organization_id"] is None + assert serialized["ip_address"] is None + assert serialized["user_agent"] is None + + def test_agent_blueprint_created_round_trip(self): + data = load_fixture("agent_blueprint_created.json") + instance = AgentBlueprintCreated.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintCreated.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_created_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.blueprint.created", + "data": { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": ["crm:read", "email:send"], + "invocable_by": { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintCreated.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_agent_blueprint_created_omits_absent_optional_non_nullable_fields(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.blueprint.created", + "data": { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": ["crm:read", "email:send"], + "invocable_by": { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintCreated.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_agent_blueprint_created_data_round_trip(self): + data = load_fixture("agent_blueprint_created_data.json") + instance = AgentBlueprintCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintCreatedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_created_data_minimal_payload(self): + data = { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": None, + "permissions": ["crm:read", "email:send"], + "invocable_by": { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["name"] == data["name"] + assert serialized["description"] == data["description"] + assert serialized["permissions"] == data["permissions"] + assert serialized["invocable_by"] == data["invocable_by"] + assert serialized["session_settings"] == data["session_settings"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_agent_blueprint_created_data_preserves_nullable_fields(self): + data = { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": None, + "permissions": ["crm:read", "email:send"], + "invocable_by": { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["description"] is None + + def test_agent_blueprint_created_data_invocable_by_round_trip(self): + data = load_fixture("agent_blueprint_created_data_invocable_by.json") + instance = AgentBlueprintCreatedDataInvocableBy.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintCreatedDataInvocableBy.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_created_data_invocable_by_minimal_payload(self): + data = { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + } + instance = AgentBlueprintCreatedDataInvocableBy.from_dict(data) + serialized = instance.to_dict() + assert serialized["role_slugs"] == data["role_slugs"] + assert serialized["organization_ids"] == data["organization_ids"] + + def test_agent_blueprint_created_data_session_setting_round_trip(self): + data = load_fixture("agent_blueprint_created_data_session_setting.json") + instance = AgentBlueprintCreatedDataSessionSetting.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintCreatedDataSessionSetting.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_created_data_session_setting_minimal_payload(self): + data = { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + } + instance = AgentBlueprintCreatedDataSessionSetting.from_dict(data) + serialized = instance.to_dict() + assert serialized["max_age_seconds"] == data["max_age_seconds"] + assert ( + serialized["access_token_ttl_seconds"] == data["access_token_ttl_seconds"] + ) + assert ( + serialized["refresh_token_ttl_seconds"] == data["refresh_token_ttl_seconds"] + ) + + def test_agent_blueprint_deleted_round_trip(self): + data = load_fixture("agent_blueprint_deleted.json") + instance = AgentBlueprintDeleted.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintDeleted.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_deleted_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.blueprint.deleted", + "data": { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintDeleted.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_agent_blueprint_deleted_omits_absent_optional_non_nullable_fields(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.blueprint.deleted", + "data": { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintDeleted.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_agent_blueprint_deleted_data_round_trip(self): + data = load_fixture("agent_blueprint_deleted_data.json") + instance = AgentBlueprintDeletedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintDeletedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_deleted_data_minimal_payload(self): + data = { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintDeletedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["name"] == data["name"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_agent_blueprint_updated_round_trip(self): + data = load_fixture("agent_blueprint_updated.json") + instance = AgentBlueprintUpdated.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintUpdated.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_updated_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.blueprint.updated", + "data": { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": ["crm:read", "email:send"], + "invocable_by": { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintUpdated.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_agent_blueprint_updated_omits_absent_optional_non_nullable_fields(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.blueprint.updated", + "data": { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": "Finds and qualifies sales prospects.", + "permissions": ["crm:read", "email:send"], + "invocable_by": { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintUpdated.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_agent_blueprint_updated_data_round_trip(self): + data = load_fixture("agent_blueprint_updated_data.json") + instance = AgentBlueprintUpdatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintUpdatedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_updated_data_minimal_payload(self): + data = { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": None, + "permissions": ["crm:read", "email:send"], + "invocable_by": { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintUpdatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["name"] == data["name"] + assert serialized["description"] == data["description"] + assert serialized["permissions"] == data["permissions"] + assert serialized["invocable_by"] == data["invocable_by"] + assert serialized["session_settings"] == data["session_settings"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_agent_blueprint_updated_data_preserves_nullable_fields(self): + data = { + "object": "agent_blueprint", + "id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "name": "Prospecting Agent", + "description": None, + "permissions": ["crm:read", "email:send"], + "invocable_by": { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + }, + "session_settings": { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentBlueprintUpdatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["description"] is None + + def test_agent_blueprint_updated_data_invocable_by_round_trip(self): + data = load_fixture("agent_blueprint_updated_data_invocable_by.json") + instance = AgentBlueprintUpdatedDataInvocableBy.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintUpdatedDataInvocableBy.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_updated_data_invocable_by_minimal_payload(self): + data = { + "role_slugs": ["manager"], + "organization_ids": ["org_01EHWNCE74X7JSDV0X3SZ3KJNY"], + } + instance = AgentBlueprintUpdatedDataInvocableBy.from_dict(data) + serialized = instance.to_dict() + assert serialized["role_slugs"] == data["role_slugs"] + assert serialized["organization_ids"] == data["organization_ids"] + + def test_agent_blueprint_updated_data_session_setting_round_trip(self): + data = load_fixture("agent_blueprint_updated_data_session_setting.json") + instance = AgentBlueprintUpdatedDataSessionSetting.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentBlueprintUpdatedDataSessionSetting.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_blueprint_updated_data_session_setting_minimal_payload(self): + data = { + "max_age_seconds": 3600, + "access_token_ttl_seconds": 300, + "refresh_token_ttl_seconds": 3600, + } + instance = AgentBlueprintUpdatedDataSessionSetting.from_dict(data) + serialized = instance.to_dict() + assert serialized["max_age_seconds"] == data["max_age_seconds"] + assert ( + serialized["access_token_ttl_seconds"] == data["access_token_ttl_seconds"] + ) + assert ( + serialized["refresh_token_ttl_seconds"] == data["refresh_token_ttl_seconds"] + ) + + def test_agent_instance_created_round_trip(self): + data = load_fixture("agent_instance_created.json") + instance = AgentInstanceCreated.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentInstanceCreated.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_instance_created_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.created", + "data": { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceCreated.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_agent_instance_created_omits_absent_optional_non_nullable_fields(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.created", + "data": { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceCreated.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_agent_instance_created_data_round_trip(self): + data = load_fixture("agent_instance_created_data.json") + instance = AgentInstanceCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentInstanceCreatedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_instance_created_data_minimal_payload(self): + data = { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": None, + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["agent_blueprint_id"] == data["agent_blueprint_id"] + assert serialized["organization_id"] == data["organization_id"] + assert ( + serialized["organization_membership_id"] + == data["organization_membership_id"] + ) + assert serialized["type"] == data["type"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_agent_instance_created_data_preserves_nullable_fields(self): + data = { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": None, + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["organization_membership_id"] is None + + def test_agent_instance_created_data_round_trips_unknown_enum_values(self): + data = { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "unexpected_agent_instance_created_data_type", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceCreatedData.from_dict(data) + assert instance.to_dict() == data + + def test_agent_instance_deleted_round_trip(self): + data = load_fixture("agent_instance_deleted.json") + instance = AgentInstanceDeleted.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentInstanceDeleted.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_instance_deleted_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.deleted", + "data": { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceDeleted.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_agent_instance_deleted_omits_absent_optional_non_nullable_fields(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.deleted", + "data": { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceDeleted.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_agent_instance_deleted_data_round_trip(self): + data = load_fixture("agent_instance_deleted_data.json") + instance = AgentInstanceDeletedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentInstanceDeletedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_instance_deleted_data_minimal_payload(self): + data = { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": None, + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceDeletedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["agent_blueprint_id"] == data["agent_blueprint_id"] + assert serialized["organization_id"] == data["organization_id"] + assert ( + serialized["organization_membership_id"] + == data["organization_membership_id"] + ) + assert serialized["type"] == data["type"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_agent_instance_deleted_data_preserves_nullable_fields(self): + data = { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": None, + "type": "delegated", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceDeletedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["organization_membership_id"] is None + + def test_agent_instance_deleted_data_round_trips_unknown_enum_values(self): + data = { + "object": "agent_instance", + "id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_blueprint_id": "agent_blueprint_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_membership_id": "om_01EHWNCE74X7JSDV0X3SZ3KJNY", + "type": "unexpected_agent_instance_deleted_data_type", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceDeletedData.from_dict(data) + assert instance.to_dict() == data + + def test_agent_instance_session_created_round_trip(self): + data = load_fixture("agent_instance_session_created.json") + instance = AgentInstanceSessionCreated.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentInstanceSessionCreated.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_instance_session_created_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.session.created", + "data": { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "permission_slugs": ["crm:read"], + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceSessionCreated.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_agent_instance_session_created_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.session.created", + "data": { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "permission_slugs": ["crm:read"], + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceSessionCreated.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_agent_instance_session_created_data_round_trip(self): + data = load_fixture("agent_instance_session_created_data.json") + instance = AgentInstanceSessionCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentInstanceSessionCreatedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_instance_session_created_data_minimal_payload(self): + data = { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "permission_slugs": ["crm:read"], + } + instance = AgentInstanceSessionCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["agent_instance_id"] == data["agent_instance_id"] + assert serialized["organization_id"] == data["organization_id"] + assert serialized["expires_at"] == data["expires_at"] + assert serialized["revoked_at"] == data["revoked_at"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + assert serialized["permission_slugs"] == data["permission_slugs"] + + def test_agent_instance_session_created_data_preserves_nullable_fields(self): + data = { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "permission_slugs": ["crm:read"], + } + instance = AgentInstanceSessionCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["revoked_at"] is None + + def test_agent_instance_session_revoked_round_trip(self): + data = load_fixture("agent_instance_session_revoked.json") + instance = AgentInstanceSessionRevoked.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AgentInstanceSessionRevoked.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_agent_instance_session_revoked_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.session.revoked", + "data": { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceSessionRevoked.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_agent_instance_session_revoked_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "agent.instance.session.revoked", + "data": { + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = AgentInstanceSessionRevoked.from_dict(data) serialized = instance.to_dict() assert "context" not in serialized - def test_action_user_registration_denied_data_round_trip(self): - data = load_fixture("action_user_registration_denied_data.json") - instance = ActionUserRegistrationDeniedData.from_dict(data) + def test_agent_instance_session_revoked_data_round_trip(self): + data = load_fixture("agent_instance_session_revoked_data.json") + instance = AgentInstanceSessionRevokedData.from_dict(data) serialized = instance.to_dict() assert serialized == data - restored = ActionUserRegistrationDeniedData.from_dict(serialized) + restored = AgentInstanceSessionRevokedData.from_dict(serialized) assert restored.to_dict() == serialized - def test_action_user_registration_denied_data_minimal_payload(self): + def test_agent_instance_session_revoked_data_minimal_payload(self): data = { - "action_endpoint_id": "action_endpoint_01EHWNCE74X7JSDV0X3SZ3KJNY", - "action_execution_id": "action_execution_01EHWNCE74X7JSDV0X3SZ3KJNY", - "type": "user_registration", - "verdict": "Deny", - "organization_id": None, - "email": "user@example.com", - "ip_address": None, - "user_agent": None, + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", } - instance = ActionUserRegistrationDeniedData.from_dict(data) + instance = AgentInstanceSessionRevokedData.from_dict(data) serialized = instance.to_dict() - assert serialized["action_endpoint_id"] == data["action_endpoint_id"] - assert serialized["action_execution_id"] == data["action_execution_id"] - assert serialized["type"] == data["type"] - assert serialized["verdict"] == data["verdict"] + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["agent_instance_id"] == data["agent_instance_id"] assert serialized["organization_id"] == data["organization_id"] - assert serialized["email"] == data["email"] - assert serialized["ip_address"] == data["ip_address"] - assert serialized["user_agent"] == data["user_agent"] + assert serialized["expires_at"] == data["expires_at"] + assert serialized["revoked_at"] == data["revoked_at"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] - def test_action_user_registration_denied_data_preserves_nullable_fields(self): + def test_agent_instance_session_revoked_data_preserves_nullable_fields(self): data = { - "action_endpoint_id": "action_endpoint_01EHWNCE74X7JSDV0X3SZ3KJNY", - "action_execution_id": "action_execution_01EHWNCE74X7JSDV0X3SZ3KJNY", - "type": "user_registration", - "verdict": "Deny", - "organization_id": None, - "email": "user@example.com", - "ip_address": None, - "user_agent": None, + "object": "agent_instance_session", + "id": "agent_session_01EHWNCE74X7JSDV0X3SZ3KJNY", + "agent_instance_id": "agent_01EHWNCE74X7JSDV0X3SZ3KJNY", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "expires_at": "2026-01-15T13:00:00.000Z", + "revoked_at": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", } - instance = ActionUserRegistrationDeniedData.from_dict(data) + instance = AgentInstanceSessionRevokedData.from_dict(data) serialized = instance.to_dict() - assert serialized["organization_id"] is None - assert serialized["ip_address"] is None - assert serialized["user_agent"] is None + assert serialized["revoked_at"] is None def test_agent_registration_claim_attempt_created_round_trip(self): data = load_fixture("agent_registration_claim_attempt_created.json") @@ -2885,6 +3787,7 @@ def test_authentication_oauth_failed_minimal_payload(self): "code": "mfa_challenge_failed", "message": "The MFA challenge has failed.", }, + "provider": "GoogleOAuth", }, "created_at": "2026-01-15T12:00:00.000Z", } @@ -2914,6 +3817,7 @@ def test_authentication_oauth_failed_omits_absent_optional_non_nullable_fields( "code": "mfa_challenge_failed", "message": "The MFA challenge has failed.", }, + "provider": "GoogleOAuth", }, "created_at": "2026-01-15T12:00:00.000Z", } @@ -2952,6 +3856,25 @@ def test_authentication_oauth_failed_data_minimal_payload(self): assert serialized["email"] == data["email"] assert serialized["error"] == data["error"] + def test_authentication_oauth_failed_data_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "type": "oauth", + "status": "failed", + "ip_address": "203.0.113.42", + "user_agent": "Mozilla/5.0", + "user_id": "user_01E4ZCR3C56J083X43JQXF3JK5", + "email": "user@example.com", + "error": { + "code": "mfa_challenge_failed", + "message": "The MFA challenge has failed.", + }, + } + instance = AuthenticationOAuthFailedData.from_dict(data) + serialized = instance.to_dict() + assert "provider" not in serialized + def test_authentication_oauth_failed_data_preserves_nullable_fields(self): data = { "type": "oauth", @@ -2964,6 +3887,7 @@ def test_authentication_oauth_failed_data_preserves_nullable_fields(self): "code": "mfa_challenge_failed", "message": "The MFA challenge has failed.", }, + "provider": "GoogleOAuth", } instance = AuthenticationOAuthFailedData.from_dict(data) serialized = instance.to_dict() @@ -3010,6 +3934,7 @@ def test_authentication_oauth_succeeded_minimal_payload(self): "user_agent": "Mozilla/5.0", "user_id": "user_01E4ZCR3C56J083X43JQXF3JK5", "email": "user@example.com", + "provider": "GoogleOAuth", }, "created_at": "2026-01-15T12:00:00.000Z", } @@ -3035,6 +3960,7 @@ def test_authentication_oauth_succeeded_omits_absent_optional_non_nullable_field "user_agent": "Mozilla/5.0", "user_id": "user_01E4ZCR3C56J083X43JQXF3JK5", "email": "user@example.com", + "provider": "GoogleOAuth", }, "created_at": "2026-01-15T12:00:00.000Z", } @@ -3068,6 +3994,21 @@ def test_authentication_oauth_succeeded_data_minimal_payload(self): assert serialized["user_id"] == data["user_id"] assert serialized["email"] == data["email"] + def test_authentication_oauth_succeeded_data_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "type": "oauth", + "status": "succeeded", + "ip_address": "203.0.113.42", + "user_agent": "Mozilla/5.0", + "user_id": "user_01E4ZCR3C56J083X43JQXF3JK5", + "email": "user@example.com", + } + instance = AuthenticationOAuthSucceededData.from_dict(data) + serialized = instance.to_dict() + assert "provider" not in serialized + def test_authentication_oauth_succeeded_data_preserves_nullable_fields(self): data = { "type": "oauth", @@ -3076,6 +4017,7 @@ def test_authentication_oauth_succeeded_data_preserves_nullable_fields(self): "user_agent": None, "user_id": None, "email": "user@example.com", + "provider": "GoogleOAuth", } instance = AuthenticationOAuthSucceededData.from_dict(data) serialized = instance.to_dict() @@ -11758,6 +12700,278 @@ def test_radar_challenge_created_data_minimal_payload(self): assert serialized["user_id"] == data["user_id"] assert serialized["email"] == data["email"] + def test_resource_export_completed_round_trip(self): + data = load_fixture("resource_export_completed.json") + instance = ResourceExportCompleted.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ResourceExportCompleted.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_resource_export_completed_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.completed", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = ResourceExportCompleted.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_resource_export_completed_omits_absent_optional_non_nullable_fields(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.completed", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = ResourceExportCompleted.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_resource_export_completed_data_round_trip(self): + data = load_fixture("resource_export_completed_data.json") + instance = ResourceExportCompletedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ResourceExportCompletedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_resource_export_completed_data_minimal_payload(self): + data = { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + } + instance = ResourceExportCompletedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["id"] == data["id"] + assert serialized["resource_type"] == data["resource_type"] + + def test_resource_export_completed_data_round_trips_unknown_enum_values(self): + data = { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "unexpected_resource_export_completed_data_resource_type", + } + instance = ResourceExportCompletedData.from_dict(data) + assert instance.to_dict() == data + + def test_resource_export_created_round_trip(self): + data = load_fixture("resource_export_created.json") + instance = ResourceExportCreated.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ResourceExportCreated.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_resource_export_created_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.created", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = ResourceExportCreated.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_resource_export_created_omits_absent_optional_non_nullable_fields(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.created", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = ResourceExportCreated.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_resource_export_created_data_round_trip(self): + data = load_fixture("resource_export_created_data.json") + instance = ResourceExportCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ResourceExportCreatedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_resource_export_created_data_minimal_payload(self): + data = { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + } + instance = ResourceExportCreatedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["id"] == data["id"] + assert serialized["resource_type"] == data["resource_type"] + + def test_resource_export_created_data_round_trips_unknown_enum_values(self): + data = { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "unexpected_resource_export_created_data_resource_type", + } + instance = ResourceExportCreatedData.from_dict(data) + assert instance.to_dict() == data + + def test_resource_export_downloaded_round_trip(self): + data = load_fixture("resource_export_downloaded.json") + instance = ResourceExportDownloaded.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ResourceExportDownloaded.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_resource_export_downloaded_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.downloaded", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = ResourceExportDownloaded.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_resource_export_downloaded_omits_absent_optional_non_nullable_fields(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.downloaded", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = ResourceExportDownloaded.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_resource_export_downloaded_data_round_trip(self): + data = load_fixture("resource_export_downloaded_data.json") + instance = ResourceExportDownloadedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ResourceExportDownloadedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_resource_export_downloaded_data_minimal_payload(self): + data = { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + } + instance = ResourceExportDownloadedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["id"] == data["id"] + assert serialized["resource_type"] == data["resource_type"] + + def test_resource_export_downloaded_data_round_trips_unknown_enum_values(self): + data = { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "unexpected_resource_export_downloaded_data_resource_type", + } + instance = ResourceExportDownloadedData.from_dict(data) + assert instance.to_dict() == data + + def test_resource_export_failed_round_trip(self): + data = load_fixture("resource_export_failed.json") + instance = ResourceExportFailed.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ResourceExportFailed.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_resource_export_failed_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.failed", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = ResourceExportFailed.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_resource_export_failed_omits_absent_optional_non_nullable_fields(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "resource_export.failed", + "data": { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = ResourceExportFailed.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_resource_export_failed_data_round_trip(self): + data = load_fixture("resource_export_failed_data.json") + instance = ResourceExportFailedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ResourceExportFailedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_resource_export_failed_data_minimal_payload(self): + data = { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "users", + } + instance = ResourceExportFailedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["id"] == data["id"] + assert serialized["resource_type"] == data["resource_type"] + + def test_resource_export_failed_data_round_trips_unknown_enum_values(self): + data = { + "id": "resource_export_01HWZBQZY2M3AMQW166Q22K88F", + "resource_type": "unexpected_resource_export_failed_data_resource_type", + } + instance = ResourceExportFailedData.from_dict(data) + assert instance.to_dict() == data + def test_role_created_round_trip(self): data = load_fixture("role_created.json") instance = RoleCreated.from_dict(data) @@ -13574,14 +14788,15 @@ def test_waitlist_user_approved_minimal_payload(self): "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", "event": "waitlist_user.approved", "data": { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": None, + "additional_fields": {"company": "Example Corp"}, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user", }, "created_at": "2026-01-15T12:00:00.000Z", } @@ -13599,14 +14814,15 @@ def test_waitlist_user_approved_omits_absent_optional_non_nullable_fields(self): "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", "event": "waitlist_user.approved", "data": { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": None, + "additional_fields": {"company": "Example Corp"}, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user", }, "created_at": "2026-01-15T12:00:00.000Z", } @@ -13628,14 +14844,15 @@ def test_waitlist_user_created_minimal_payload(self): "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", "event": "waitlist_user.created", "data": { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": None, + "additional_fields": {"company": "Example Corp"}, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user", }, "created_at": "2026-01-15T12:00:00.000Z", } @@ -13653,14 +14870,15 @@ def test_waitlist_user_created_omits_absent_optional_non_nullable_fields(self): "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", "event": "waitlist_user.created", "data": { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": None, + "additional_fields": {"company": "Example Corp"}, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user", }, "created_at": "2026-01-15T12:00:00.000Z", } @@ -13682,14 +14900,15 @@ def test_waitlist_user_denied_minimal_payload(self): "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", "event": "waitlist_user.denied", "data": { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": None, + "additional_fields": {"company": "Example Corp"}, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user", }, "created_at": "2026-01-15T12:00:00.000Z", } @@ -13707,14 +14926,15 @@ def test_waitlist_user_denied_omits_absent_optional_non_nullable_fields(self): "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", "event": "waitlist_user.denied", "data": { - "object": "waitlist_user", "id": "wl_user_01E4ZCR3C56J083X43JQXF3JK5", "email": "marcelina.davis@example.com", "state": "pending", "approved_at": None, + "additional_fields": {"company": "Example Corp"}, "waitlist_id": "waitlist_01E4ZCR3C56J083X43JQXF3JK5", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", + "object": "waitlist_user", }, "created_at": "2026-01-15T12:00:00.000Z", } diff --git a/tests/test_organization_membership.py b/tests/test_organization_membership.py index e66e998e..3d3eb852 100644 --- a/tests/test_organization_membership.py +++ b/tests/test_organization_membership.py @@ -68,7 +68,7 @@ def test_create_organization_membership(self, workos, httpx_mock): result = workos.organization_membership.create_organization_membership( user_id="test_user_id", organization_id="test_organization_id", - role=RoleSingle(role_slug="test_value"), + role=RoleSingle(role_slug="test_role_slug"), ) assert isinstance(result, OrganizationMembership) assert result.object == "organization_membership" @@ -99,7 +99,7 @@ def test_update_organization_membership(self, workos, httpx_mock): json=load_fixture("user_organization_membership.json"), ) result = workos.organization_membership.update_organization_membership( - "test_id", role=RoleSingle(role_slug="test_value") + "test_id", role=RoleSingle(role_slug="test_role_slug") ) assert isinstance(result, UserOrganizationMembership) assert result.object == "organization_membership" @@ -322,7 +322,7 @@ async def test_create_organization_membership(self, async_workos, httpx_mock): await async_workos.organization_membership.create_organization_membership( user_id="test_user_id", organization_id="test_organization_id", - role=RoleSingle(role_slug="test_value"), + role=RoleSingle(role_slug="test_role_slug"), ) ) assert isinstance(result, OrganizationMembership) @@ -352,7 +352,7 @@ async def test_update_organization_membership(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("user_organization_membership.json")) result = ( await async_workos.organization_membership.update_organization_membership( - "test_id", role=RoleSingle(role_slug="test_value") + "test_id", role=RoleSingle(role_slug="test_role_slug") ) ) assert isinstance(result, UserOrganizationMembership) diff --git a/tests/test_organizations.py b/tests/test_organizations.py index 8c2bd654..975d01fb 100644 --- a/tests/test_organizations.py +++ b/tests/test_organizations.py @@ -18,6 +18,8 @@ from workos.common.models import PaginationOrder from workos.organizations.models import ( AuditLogConfiguration, + ItContact, + ItContactList, Organization, OrganizationAuthorizedConnectApplicationListData, ) @@ -167,6 +169,71 @@ def test_list_authorized_applications_encodes_query_params( assert request.url.params["after"] == "cursor/after" assert request.url.params["order"] == "value_order" + def test_list_it_contacts(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("it_contact_list.json"), + ) + result = workos.organizations.list_it_contacts("test_organization_id") + assert isinstance(result, ItContactList) + assert result.object == "list" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/organizations/test_organization_id/it_contacts" + ) + + def test_create_it_contact(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("it_contact.json"), + ) + result = workos.organizations.create_it_contact( + "test_organization_id", email="test_email" + ) + assert isinstance(result, ItContact) + assert result.object == "it_contact" + assert result.id == "it_contact_01HXYZ123456789ABCDEFGHIJ" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/organizations/test_organization_id/it_contacts" + ) + body = json.loads(request.content) + assert body["email"] == "test_email" + + def test_delete_it_contact(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.organizations.delete_it_contact( + "test_organization_id", "test_contact_id" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/organizations/test_organization_id/it_contacts/test_contact_id" + ) + + def test_invite_it_contact(self, workos, httpx_mock): + httpx_mock.add_response(json={}) + workos.organizations.invite_it_contact( + "test_organization_id", "test_contact_id", intents=[] + ) + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/organizations/test_organization_id/it_contacts/test_contact_id/invite" + ) + + def test_revoke_it_contact(self, workos, httpx_mock): + httpx_mock.add_response(json={}) + workos.organizations.revoke_it_contact( + "test_organization_id", "test_contact_id" + ) + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/organizations/test_organization_id/it_contacts/test_contact_id/revoke" + ) + def test_list_organizations_with_request_options(self, workos, httpx_mock): httpx_mock.add_response(json={"data": [], "list_metadata": {}}) workos.organizations.list_organizations( @@ -393,6 +460,72 @@ async def test_list_authorized_applications_encodes_query_params( assert request.url.params["after"] == "cursor/after" assert request.url.params["order"] == "value_order" + @pytest.mark.asyncio + async def test_list_it_contacts(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("it_contact_list.json")) + result = await async_workos.organizations.list_it_contacts( + "test_organization_id" + ) + assert isinstance(result, ItContactList) + assert result.object == "list" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/organizations/test_organization_id/it_contacts" + ) + + @pytest.mark.asyncio + async def test_create_it_contact(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("it_contact.json")) + result = await async_workos.organizations.create_it_contact( + "test_organization_id", email="test_email" + ) + assert isinstance(result, ItContact) + assert result.object == "it_contact" + assert result.id == "it_contact_01HXYZ123456789ABCDEFGHIJ" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/organizations/test_organization_id/it_contacts" + ) + + @pytest.mark.asyncio + async def test_delete_it_contact(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = await async_workos.organizations.delete_it_contact( + "test_organization_id", "test_contact_id" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/organizations/test_organization_id/it_contacts/test_contact_id" + ) + + @pytest.mark.asyncio + async def test_invite_it_contact(self, async_workos, httpx_mock): + httpx_mock.add_response(json={}) + await async_workos.organizations.invite_it_contact( + "test_organization_id", "test_contact_id", intents=[] + ) + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/organizations/test_organization_id/it_contacts/test_contact_id/invite" + ) + + @pytest.mark.asyncio + async def test_revoke_it_contact(self, async_workos, httpx_mock): + httpx_mock.add_response(json={}) + await async_workos.organizations.revoke_it_contact( + "test_organization_id", "test_contact_id" + ) + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/organizations/test_organization_id/it_contacts/test_contact_id/revoke" + ) + @pytest.mark.asyncio async def test_list_organizations_with_request_options( self, async_workos, httpx_mock diff --git a/tests/test_organizations_models_round_trip.py b/tests/test_organizations_models_round_trip.py index e9a218a5..1befe5cd 100644 --- a/tests/test_organizations_models_round_trip.py +++ b/tests/test_organizations_models_round_trip.py @@ -7,6 +7,9 @@ AuditLogConfiguration, AuditLogConfigurationLogStream, AuditLogsRetention, + ItContact, + ItContactList, + ItContactListListMetadata, Organization, OrganizationAuthorizedConnectApplicationListData, OrganizationDomainData, @@ -59,6 +62,61 @@ def test_audit_logs_retention_preserves_nullable_fields(self): serialized = instance.to_dict() assert serialized["retention_period_in_days"] is None + def test_it_contact_round_trip(self): + data = load_fixture("it_contact.json") + instance = ItContact.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ItContact.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_it_contact_minimal_payload(self): + data = { + "object": "it_contact", + "id": "it_contact_01HXYZ123456789ABCDEFGHIJ", + "email": "it-contact@example.com", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = ItContact.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["email"] == data["email"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_it_contact_list_round_trip(self): + data = load_fixture("it_contact_list.json") + instance = ItContactList.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ItContactList.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_it_contact_list_minimal_payload(self): + data = { + "object": "list", + "data": [ + { + "object": "it_contact", + "id": "it_contact_01HXYZ123456789ABCDEFGHIJ", + "email": "it-contact@example.com", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + ], + "list_metadata": { + "before": "it_contact_01HXYZ123456789ABCDEFGHIJ", + "after": "it_contact_01HXYZ987654321KJIHGFEDCBA", + }, + } + instance = ItContactList.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["data"] == data["data"] + assert serialized["list_metadata"] == data["list_metadata"] + def test_organization_round_trip(self): data = load_fixture("organization.json") instance = Organization.from_dict(data) @@ -256,6 +314,28 @@ def test_audit_log_configuration_log_stream_round_trips_unknown_enum_values(self instance = AuditLogConfigurationLogStream.from_dict(data) assert instance.to_dict() == data + def test_it_contact_list_list_metadata_round_trip(self): + data = load_fixture("it_contact_list_list_metadata.json") + instance = ItContactListListMetadata.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ItContactListListMetadata.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_it_contact_list_list_metadata_minimal_payload(self): + data = {"before": None, "after": None} + instance = ItContactListListMetadata.from_dict(data) + serialized = instance.to_dict() + assert serialized["before"] == data["before"] + assert serialized["after"] == data["after"] + + def test_it_contact_list_list_metadata_preserves_nullable_fields(self): + data = {"before": None, "after": None} + instance = ItContactListListMetadata.from_dict(data) + serialized = instance.to_dict() + assert serialized["before"] is None + assert serialized["after"] is None + def test_organization_authorized_connect_application_list_data_round_trip(self): data = load_fixture( "organization_authorized_connect_application_list_data.json" diff --git a/tests/test_platform_teams.py b/tests/test_platform_teams.py new file mode 100644 index 00000000..3c2a96e5 --- /dev/null +++ b/tests/test_platform_teams.py @@ -0,0 +1,256 @@ +# This file is auto-generated by oagen. Do not edit. + +import json + +import pytest + +from tests.generated_helpers import load_fixture +from workos import AsyncWorkOSClient, WorkOSClient +from workos._errors import ( + AuthenticationError, + BadRequestError, + NotFoundError, + RateLimitExceededError, + ServerError, + UnprocessableEntityError, +) +from workos.platform_teams.models import Team + + +class TestPlatformTeams: + def test_create_team(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("team.json"), + ) + result = workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + assert isinstance(result, Team) + assert result.object == "team" + assert result.id == "team_01JX9AN6E02HAG2Q2CKGC1XT5W" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/platform/teams") + body = json.loads(request.content) + assert body["admin_email"] == "test_admin_email" + assert body["name"] == "test_name" + + def test_get_team(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("team.json"), + ) + result = workos.platform_teams.get_team("test_team_id") + assert isinstance(result, Team) + assert result.object == "team" + assert result.id == "team_01JX9AN6E02HAG2Q2CKGC1XT5W" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/platform/teams/test_team_id") + + def test_create_team_with_request_options(self, workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("team.json")) + workos.platform_teams.create_team( + admin_email="test_admin_email", + name="test_name", + request_options={"extra_headers": {"X-Custom": "value"}}, + ) + request = httpx_mock.get_request() + assert request.headers["X-Custom"] == "value" + + def test_create_team_unauthorized(self, workos, httpx_mock): + httpx_mock.add_response( + status_code=401, + json={"message": "Unauthorized"}, + ) + with pytest.raises(AuthenticationError): + workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + + def test_create_team_not_found(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=404, json={"message": "Not found"}) + with pytest.raises(NotFoundError): + workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + finally: + workos.close() + + def test_create_team_rate_limited(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response( + status_code=429, + headers={"Retry-After": "0"}, + json={"message": "Slow down"}, + ) + with pytest.raises(RateLimitExceededError): + workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + finally: + workos.close() + + def test_create_team_server_error(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=500, json={"message": "Server error"}) + with pytest.raises(ServerError): + workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + finally: + workos.close() + + def test_create_team_bad_request(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) + with pytest.raises(BadRequestError): + workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + finally: + workos.close() + + def test_create_team_unprocessable(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) + with pytest.raises(UnprocessableEntityError): + workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + finally: + workos.close() + + +class TestAsyncPlatformTeams: + @pytest.mark.asyncio + async def test_create_team(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("team.json")) + result = await async_workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + assert isinstance(result, Team) + assert result.object == "team" + assert result.id == "team_01JX9AN6E02HAG2Q2CKGC1XT5W" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/platform/teams") + + @pytest.mark.asyncio + async def test_get_team(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("team.json")) + result = await async_workos.platform_teams.get_team("test_team_id") + assert isinstance(result, Team) + assert result.object == "team" + assert result.id == "team_01JX9AN6E02HAG2Q2CKGC1XT5W" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/platform/teams/test_team_id") + + @pytest.mark.asyncio + async def test_create_team_with_request_options(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("team.json")) + await async_workos.platform_teams.create_team( + admin_email="test_admin_email", + name="test_name", + request_options={"extra_headers": {"X-Custom": "value"}}, + ) + request = httpx_mock.get_request() + assert request.headers["X-Custom"] == "value" + + @pytest.mark.asyncio + async def test_create_team_unauthorized(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=401, json={"message": "Unauthorized"}) + with pytest.raises(AuthenticationError): + await async_workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + + @pytest.mark.asyncio + async def test_create_team_not_found(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=404, json={"message": "Not found"}) + with pytest.raises(NotFoundError): + await workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_create_team_rate_limited(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response( + status_code=429, + headers={"Retry-After": "0"}, + json={"message": "Slow down"}, + ) + with pytest.raises(RateLimitExceededError): + await workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_create_team_server_error(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=500, json={"message": "Server error"}) + with pytest.raises(ServerError): + await workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_create_team_bad_request(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) + with pytest.raises(BadRequestError): + await workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_create_team_unprocessable(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) + with pytest.raises(UnprocessableEntityError): + await workos.platform_teams.create_team( + admin_email="test_admin_email", name="test_name" + ) + finally: + await workos.close() diff --git a/tests/test_platform_teams_models_round_trip.py b/tests/test_platform_teams_models_round_trip.py new file mode 100644 index 00000000..b1ac0f7d --- /dev/null +++ b/tests/test_platform_teams_models_round_trip.py @@ -0,0 +1,63 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Model round-trip tests: from_dict(to_dict()) preserves data.""" + +from tests.generated_helpers import load_fixture +from workos.platform_teams.models import Team + + +class TestModelRoundTrip: + def test_team_round_trip(self): + data = load_fixture("team.json") + instance = Team.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = Team.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_team_minimal_payload(self): + data = { + "object": "team", + "id": "team_01JX9AN6E02HAG2Q2CKGC1XT5W", + "name": "Example Team", + "production_state": "Active", + "production_enabled_at": None, + "created_at": "2024-01-01T00:00:00.000Z", + "updated_at": "2024-01-01T00:00:00.000Z", + } + instance = Team.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["name"] == data["name"] + assert serialized["production_state"] == data["production_state"] + assert serialized["production_enabled_at"] == data["production_enabled_at"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_team_preserves_nullable_fields(self): + data = { + "object": "team", + "id": "team_01JX9AN6E02HAG2Q2CKGC1XT5W", + "name": "Example Team", + "production_state": "Active", + "production_enabled_at": None, + "created_at": "2024-01-01T00:00:00.000Z", + "updated_at": "2024-01-01T00:00:00.000Z", + } + instance = Team.from_dict(data) + serialized = instance.to_dict() + assert serialized["production_enabled_at"] is None + + def test_team_round_trips_unknown_enum_values(self): + data = { + "object": "team", + "id": "team_01JX9AN6E02HAG2Q2CKGC1XT5W", + "name": "Example Team", + "production_state": "unexpected_team_production_state", + "production_enabled_at": "2024-01-01T00:00:00.000Z", + "created_at": "2024-01-01T00:00:00.000Z", + "updated_at": "2024-01-01T00:00:00.000Z", + } + instance = Team.from_dict(data) + assert instance.to_dict() == data From 8143875043bb528b9e93abae8c52253fea1a99d8 Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:10:04 +0000 Subject: [PATCH 8/8] chore(generated): add release notes fragment --- ...67627615ba0a9db28194f68afd773d08ce9cbe7.md | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 .changelog-pending/2026-09-01T16-10-04-667627615ba0a9db28194f68afd773d08ce9cbe7.md diff --git a/.changelog-pending/2026-09-01T16-10-04-667627615ba0a9db28194f68afd773d08ce9cbe7.md b/.changelog-pending/2026-09-01T16-10-04-667627615ba0a9db28194f68afd773d08ce9cbe7.md new file mode 100644 index 00000000..44fbef1c --- /dev/null +++ b/.changelog-pending/2026-09-01T16-10-04-667627615ba0a9db28194f68afd773d08ce9cbe7.md @@ -0,0 +1,141 @@ +* [#719](https://github.com/workos/workos-python/pull/719) feat(generated)!: regenerate from spec (6 changes) + + **⚠️ Breaking** + * **[agents](https://workos.com/docs/reference/agents)**: + * Removed service `Agents` + * **[sso](https://workos.com/docs/reference/sso)**: + * Removed model `TokenBody` + * Removed enum `TokenBodyGrantType` + * Removed model `TokenQuery` + * Changed parameter `SSO.token.grant_type` + + **Features** + * **[agents](https://workos.com/docs/reference/agents)**: + * Added model `AgentBlueprint` + * Added model `AgentInstance` + * Added model `AgentToken` + * Added model `AgentInstanceSession` + * Added model `AgentBlueprintInvocableBy` + * Added model `AgentBlueprintSessionSetting` + * Added model `AgentBlueprintsCreateRequest` + * Added model `AgentBlueprintsCreateRequestInvocableBy` + * Added model `AgentBlueprintsCreateRequestSessionSetting` + * Added model `AgentBlueprintsUpdateRequest` + * Added model `AgentBlueprintsUpdateRequestInvocableBy` + * Added model `AgentBlueprintsUpdateRequestSessionSetting` + * Added model `AgentBlueprintsTokenMintTokenRequest` + * Added enum `AgentInstanceType` + * Added enum `AgentInstanceSessionStatus` + * Added enum `AgentBlueprintsTokenMintTokenRequestType` + * **agents_blueprints**: + * Added service `AgentsBlueprints` + * **agents_blueprints_tokens**: + * Added service `AgentsBlueprintsTokens` + * **agents_instances**: + * Added service `AgentsInstances` + * **agents_registrations**: + * Added service `AgentsRegistrations` + * **agents_sessions**: + * Added service `AgentsSessions` + * **[organizations](https://workos.com/docs/reference/organization)**: + * Added `retention_period` to `UpdateAuditLogsRetention` + * Made `UpdateAuditLogsRetention.retention_period_in_days` optional + * Added enum `UpdateAuditLogsRetentionRetentionPeriod` + * Added model `CreateItContact` + * Added model `InviteItContact` + * Added model `ItContact` + * Added model `ItContactList` + * Added model `ItContactListListMetadata` + * Added enum `InviteItContactIntents` + * Added service `OrganizationsItContacts` + * **platform_teams**: + * Added model `CreateTeam` + * Added model `Team` + * Added enum `TeamProductionState` + * Added service `PlatformTeams` + * **[sso](https://workos.com/docs/reference/sso)**: + * Added model `TokenQuery` + * Added enum `TokenQueryGrantType` + * Added model `CreateConnectionKeyPair` + * Added model `CreateConnectionSAMLOptions` + * Added model `CreateConnectionOidcOptions` + * Added model `CreateConnectionStandardAttributes` + * Added model `CreateConnectionAttributeMaps` + * Added model `CreateConnection` + * Added model `PatchConnectionSAMLOptions` + * Added model `PatchConnectionOidcOptions` + * Added model `PatchConnectionStandardAttributes` + * Added model `PatchConnectionAttributeMaps` + * Added model `PatchConnection` + * Added model `CreateSAMLIdpSigningCertificate` + * Added model `SAMLIdpSigningCertificate` + * Added model `SAMLIdpSigningCertificateList` + * Added model `SAMLSpEncryptionCertificate` + * Added model `SAMLSpEncryptionCertificateList` + * Added model `SAMLSpSigningCertificate` + * Added enum `CreateConnectionOidcOptionsIdTokenSignatureAlgorithm` + * Added enum `PatchConnectionOidcOptionsIdTokenSignatureAlgorithm` + * Added endpoint `POST /connections` + * Added endpoint `GET /connections/{connectionId}/saml_idp_signing_certs` + * Added endpoint `POST /connections/{connectionId}/saml_idp_signing_certs` + * Added endpoint `DELETE /connections/{connectionId}/saml_idp_signing_certs/{certificateId}` + * Added endpoint `GET /connections/{connectionId}/saml_sp_encryption_certs` + * Added endpoint `POST /connections/{connectionId}/saml_sp_encryption_certs` + * Added endpoint `DELETE /connections/{connectionId}/saml_sp_encryption_certs/{certificateId}` + * Added endpoint `GET /connections/{connectionId}/saml_sp_signing_cert` + * Added endpoint `POST /connections/{connectionId}/saml_sp_signing_cert` + * Added endpoint `DELETE /connections/{connectionId}/saml_sp_signing_cert/{certificateId}` + * Added endpoint `PATCH /connections/{id}` + * Added model `TokenBody` + * Added enum `TokenBodyGrantType` + * Added enum `SSOGrantType` + * Changed required status for parameter `SSO.token.code` + * **[user_management](https://workos.com/docs/reference/authkit/user)**: + * Added enum `CreateConnectionOidcOptionsTokenAuthenticationMethod` + * Added enum `PatchConnectionOidcOptionsTokenAuthenticationMethod` + * Added model `EmailCompletionSessionAuthenticateRequest` + * Added model `CreateWaitlistEntry` + * Added model `Waitlist` + * Added model `WaitlistEntry` + * Added enum `WaitlistEntryState` + * Added enum `UserManagementWaitlistsState` + * Added service `UserManagementWaitlists` + * **[webhooks](https://workos.com/docs/reference/webhooks)**: + * Added `agent.instance.created` to `CreateWebhookEndpointEvents` + * Added `agent.instance.deleted` to `CreateWebhookEndpointEvents` + * Added `agent.instance.session.created` to `CreateWebhookEndpointEvents` + * Added `agent.instance.session.revoked` to `CreateWebhookEndpointEvents` + * Added `agent.instance.created` to `UpdateWebhookEndpointEvents` + * Added `agent.instance.deleted` to `UpdateWebhookEndpointEvents` + * Added `agent.instance.session.created` to `UpdateWebhookEndpointEvents` + * Added `agent.instance.session.revoked` to `UpdateWebhookEndpointEvents` + * Added `agent.blueprint.created` to `CreateWebhookEndpointEvents` + * Added `agent.blueprint.deleted` to `CreateWebhookEndpointEvents` + * Added `agent.blueprint.updated` to `CreateWebhookEndpointEvents` + * Added `agent.blueprint.created` to `UpdateWebhookEndpointEvents` + * Added `agent.blueprint.deleted` to `UpdateWebhookEndpointEvents` + * Added `agent.blueprint.updated` to `UpdateWebhookEndpointEvents` + + **Fixes** + * **[sso](https://workos.com/docs/reference/sso)**: + * Changed request body of `SSO.token` from `TokenBody` to `TokenQuery` + * Removed `DiscordOAuth` from `ConnectionType` + * Removed `GrokOAuth` from `ConnectionType` + * Removed `XOAuth` from `ConnectionType` + * Removed `DiscordOAuth` from `ProfileConnectionType` + * Removed `GrokOAuth` from `ProfileConnectionType` + * Removed `XOAuth` from `ProfileConnectionType` + * Removed `DiscordOAuth` from `ConnectionsConnectionType` + * Removed `GrokOAuth` from `ConnectionsConnectionType` + * Removed `XOAuth` from `ConnectionsConnectionType` + * Changed request body of `SSO.token` from `TokenQuery` to `TokenBody` + * **[user_management](https://workos.com/docs/reference/authkit/user)**: + * Changed request body for `UserManagementAuthentication.authenticate` + * Changed errors for endpoint `POST /user_management/authenticate` + * Removed `DiscordOAuth` from `AuthenticateResponseAuthenticationMethod` + * Removed `GrokOAuth` from `AuthenticateResponseAuthenticationMethod` + * Removed `XOAuth` from `AuthenticateResponseAuthenticationMethod` + * Removed `DiscordOAuth` from `UserIdentitiesGetItemProvider` + * Removed `GrokOAuth` from `UserIdentitiesGetItemProvider` + * Removed `XOAuth` from `UserIdentitiesGetItemProvider` + * Changed errors for endpoint `DELETE /user_management/users/{id}`