diff --git a/dashscope/agentstudio/__init__.py b/dashscope/agentstudio/__init__.py index dd0403d..2a8a3b2 100644 --- a/dashscope/agentstudio/__init__.py +++ b/dashscope/agentstudio/__init__.py @@ -70,6 +70,15 @@ DeploymentCreateParams, DeploymentUpdateParams, SessionEventSendParams, + WebhookEndpointCreateParams, + WebhookEndpointUpdateParams, + WebhookEventListParams, +) +from .constants import ( + WebhookDeliveryStatus, + WebhookDisabledReason, + WebhookEventType, + WebhookStatus, ) __all__ = [ @@ -119,4 +128,11 @@ "DeploymentCreateParams", "DeploymentUpdateParams", "SessionEventSendParams", + "WebhookEndpointCreateParams", + "WebhookEndpointUpdateParams", + "WebhookEventListParams", + "WebhookStatus", + "WebhookDisabledReason", + "WebhookDeliveryStatus", + "WebhookEventType", ] diff --git a/dashscope/agentstudio/client.py b/dashscope/agentstudio/client.py index 04be3fa..9b01394 100644 --- a/dashscope/agentstudio/client.py +++ b/dashscope/agentstudio/client.py @@ -24,6 +24,10 @@ from dashscope.agentstudio.resources.sessions import Sessions, AsyncSessions from dashscope.agentstudio.resources.skills import Skills, AsyncSkills from dashscope.agentstudio.resources.vaults import Vaults, AsyncVaults +from dashscope.agentstudio.resources.webhook_endpoints import ( + AsyncWebhookEndpoints, + WebhookEndpoints, +) from dashscope.agentstudio.constants import ( AGENTSTUDIO_BASE_URL_TEMPLATE, AGENTSTUDIO_DEFAULT_REGION, @@ -129,6 +133,7 @@ def __init__( self.files = Files(self) self.skills = Skills(self) self.vaults = Vaults(self) + self.webhook_endpoints = WebhookEndpoints(self) def close(self) -> None: self.transport.close() @@ -201,6 +206,7 @@ def __init__( self.files = AsyncFiles(self) self.skills = AsyncSkills(self) self.vaults = AsyncVaults(self) + self.webhook_endpoints = AsyncWebhookEndpoints(self) async def aclose(self) -> None: await self.transport.aclose() diff --git a/dashscope/agentstudio/constants.py b/dashscope/agentstudio/constants.py index f589155..97c50b1 100644 --- a/dashscope/agentstudio/constants.py +++ b/dashscope/agentstudio/constants.py @@ -93,3 +93,113 @@ class SessionStatus(StrEnum): RUNNING = "running" RESCHEDULING = "rescheduling" TERMINATED = "terminated" + + +class WebhookStatus(StrEnum): + """Webhook endpoint activation status.""" + + # The endpoint can receive webhook deliveries. + ACTIVE = "ACTIVE" + # The endpoint does not receive webhook deliveries. + DISABLED = "DISABLED" + + +class WebhookDisabledReason(StrEnum): + """Reason why a webhook endpoint was disabled.""" + + # The endpoint was disabled manually. + MANUAL = "MANUAL" + # The endpoint reached the consecutive delivery failure threshold. + CONSECUTIVE_DELIVERY_FAILURES = "CONSECUTIVE_DELIVERY_FAILURES" + # The endpoint returned an HTTP redirect response. + REDIRECT_RESPONSE = "REDIRECT_RESPONSE" + # The endpoint failed server-side request forgery validation. + SSRF_VALIDATION_FAILED = "SSRF_VALIDATION_FAILED" + # The endpoint failed Transport Layer Security validation. + TLS_VALIDATION_FAILED = "TLS_VALIDATION_FAILED" + + +class WebhookDeliveryStatus(StrEnum): + """Webhook delivery status.""" + + # The delivery is waiting to start. + PENDING = "PENDING" + # The delivery is currently being sent. + DELIVERING = "DELIVERING" + # The delivery is waiting for its next retry. + WAITING_RETRY = "WAITING_RETRY" + # The delivery completed successfully. + SUCCEEDED = "SUCCEEDED" + # The delivery exhausted all attempts. + FAILED = "FAILED" + # The delivery was canceled because its endpoint was disabled or deleted. + CANCELED = "CANCELED" + + +class WebhookEventType(StrEnum): + """Event types accepted by webhook endpoint subscriptions.""" + + # A session was created. + SESSION_CREATED = "session.created" + # A session was updated. + SESSION_UPDATED = "session.updated" + # A session was archived. + SESSION_ARCHIVED = "session.archived" + # A session was deleted. + SESSION_DELETED = "session.deleted" + # A session run started. + SESSION_STATUS_RUN_STARTED = "session.status_run_started" + # A session entered the idle state. + SESSION_STATUS_IDLED = "session.status_idled" + # A session entered the terminated state. + SESSION_STATUS_TERMINATED = "session.status_terminated" + # A session thread was created. + SESSION_THREAD_CREATED = "session.thread_created" + # A session thread run started. + SESSION_THREAD_RUN_STARTED = "session.thread_run_started" + # A session thread entered the idle state. + SESSION_THREAD_IDLED = "session.thread_idled" + # A session thread entered the terminated state. + SESSION_THREAD_TERMINATED = "session.thread_terminated" + # An agent was created. + AGENT_CREATED = "agent.created" + # An agent was updated. + AGENT_UPDATED = "agent.updated" + # An agent was archived. + AGENT_ARCHIVED = "agent.archived" + # A deployment was created. + DEPLOYMENT_CREATED = "deployment.created" + # A deployment was updated. + DEPLOYMENT_UPDATED = "deployment.updated" + # A deployment was archived. + DEPLOYMENT_ARCHIVED = "deployment.archived" + # A deployment was paused. + DEPLOYMENT_PAUSED = "deployment.paused" + # A deployment was resumed. + DEPLOYMENT_UNPAUSED = "deployment.unpaused" + # A deployment run started. + DEPLOYMENT_RUN_STARTED = "deployment_run.started" + # A deployment run failed. + DEPLOYMENT_RUN_FAILED = "deployment_run.failed" + # A deployment run succeeded. + DEPLOYMENT_RUN_SUCCEEDED = "deployment_run.succeeded" + # An environment was created. + ENVIRONMENT_CREATED = "environment.created" + # An environment was updated. + ENVIRONMENT_UPDATED = "environment.updated" + # An environment was archived. + ENVIRONMENT_ARCHIVED = "environment.archived" + # An environment was deleted. + ENVIRONMENT_DELETED = "environment.deleted" + # A vault was created. + VAULT_CREATED = "vault.created" + # A vault was archived. + VAULT_ARCHIVED = "vault.archived" + # A vault was deleted. + VAULT_DELETED = "vault.deleted" + # A vault credential was created. + VAULT_CREDENTIAL_CREATED = "vault_credential.created" + # A vault credential was archived. + VAULT_CREDENTIAL_ARCHIVED = "vault_credential.archived" + # A vault credential was deleted. + VAULT_CREDENTIAL_DELETED = "vault_credential.deleted" diff --git a/dashscope/agentstudio/resources/__init__.py b/dashscope/agentstudio/resources/__init__.py index 78cef81..0e22c24 100644 --- a/dashscope/agentstudio/resources/__init__.py +++ b/dashscope/agentstudio/resources/__init__.py @@ -46,6 +46,10 @@ Credentials, Vaults, ) +from dashscope.agentstudio.resources.webhook_endpoints import ( + AsyncWebhookEndpoints, + WebhookEndpoints, +) __all__ = [ "Agents", @@ -70,6 +74,8 @@ "AsyncSessionEvents", "Vaults", "AsyncVaults", + "WebhookEndpoints", + "AsyncWebhookEndpoints", "_TypedEventStream", "_AioTypedEventStream", ] diff --git a/dashscope/agentstudio/resources/_helpers.py b/dashscope/agentstudio/resources/_helpers.py index 660f9a6..4fd8a7a 100644 --- a/dashscope/agentstudio/resources/_helpers.py +++ b/dashscope/agentstudio/resources/_helpers.py @@ -19,6 +19,9 @@ Skill, SkillVersion, Vault, + WebhookEndpoint, + WebhookEvent, + WebhookSecretReset, parse_server_event, ) @@ -68,6 +71,20 @@ def _coerce_credential(payload: Mapping[str, Any]) -> Credential: return Credential(**dict(payload)) +def _coerce_webhook_endpoint(payload: Mapping[str, Any]) -> WebhookEndpoint: + return WebhookEndpoint(**dict(payload)) + + +def _coerce_webhook_event(payload: Mapping[str, Any]) -> WebhookEvent: + return WebhookEvent(**dict(payload)) + + +def _coerce_webhook_secret_reset( + payload: Mapping[str, Any], +) -> WebhookSecretReset: + return WebhookSecretReset(**dict(payload)) + + def _coerce_deployment(payload: Mapping[str, Any]) -> Deployment: return Deployment(**dict(payload)) diff --git a/dashscope/agentstudio/resources/webhook_endpoints.py b/dashscope/agentstudio/resources/webhook_endpoints.py new file mode 100644 index 0000000..fc1a448 --- /dev/null +++ b/dashscope/agentstudio/resources/webhook_endpoints.py @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Alibaba, Inc. and its affiliates. +"""Managed Agent webhook endpoint resource classes.""" + +from __future__ import annotations + +from typing import Any, Dict, Optional, Sequence + +from dashscope.agentstudio.pagination import ( + AsyncCursorPage, + CursorPage, + build_page, +) +from dashscope.agentstudio.resources._helpers import ( + _coerce_webhook_endpoint, + _coerce_webhook_event, + _coerce_webhook_secret_reset, +) +from dashscope.agentstudio.types import ( + WebhookEndpoint, + WebhookEndpointList, + WebhookEvent, + WebhookSecretReset, +) +from dashscope.agentstudio.types.params import ( + WebhookEndpointCreateParams, + WebhookEndpointUpdateParams, + WebhookEventListParams, +) + + +# Relative path of the webhook endpoint collection. +_PATH_WEBHOOK_ENDPOINTS = "/webhook_endpoints" + + +class WebhookEndpoints: + """Synchronous Managed Agent webhook endpoint APIs.""" + + def __init__(self, client) -> None: + self._client = client + + def create( + self, + *, + url: str, + events: Sequence[str], + description: Optional[str] = None, + ) -> WebhookEndpoint: + body = WebhookEndpointCreateParams( + url=url, + events=list(events), + description=description, + ).to_dict() + response = self._client.transport.request( + "POST", + _PATH_WEBHOOK_ENDPOINTS, + json=body, + ) + return _coerce_webhook_endpoint(response.data) + + def retrieve(self, webhook_id: str) -> WebhookEndpoint: + response = self._client.transport.request( + "GET", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}", + ) + return _coerce_webhook_endpoint(response.data) + + get = retrieve + + def list(self) -> WebhookEndpointList: + response = self._client.transport.request( + "GET", + _PATH_WEBHOOK_ENDPOINTS, + ) + return WebhookEndpointList(**response.data) + + def update( + self, + webhook_id: str, + *, + description: Optional[str] = None, + url: Optional[str] = None, + events: Optional[Sequence[str]] = None, + ) -> WebhookEndpoint: + body = WebhookEndpointUpdateParams( + description=description, + url=url, + events=list(events) if events is not None else None, + ).to_dict() + response = self._client.transport.request( + "PUT", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}", + json=body, + ) + return _coerce_webhook_endpoint(response.data) + + def delete(self, webhook_id: str) -> Dict[str, Any]: + response = self._client.transport.request( + "DELETE", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}", + ) + return response.data + + def enable(self, webhook_id: str) -> WebhookEndpoint: + response = self._client.transport.request( + "POST", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}/enable", + ) + return _coerce_webhook_endpoint(response.data) + + def disable(self, webhook_id: str) -> WebhookEndpoint: + response = self._client.transport.request( + "POST", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}/disable", + ) + return _coerce_webhook_endpoint(response.data) + + def test(self, webhook_id: str) -> WebhookEvent: + response = self._client.transport.request( + "POST", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}/test", + ) + return _coerce_webhook_event(response.data) + + def reset_secret(self, webhook_id: str) -> WebhookSecretReset: + response = self._client.transport.request( + "POST", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}/reset_secret", + ) + return _coerce_webhook_secret_reset(response.data) + + def list_events( + self, + webhook_id: str, + *, + limit: Optional[int] = None, + page: Optional[str] = None, + ) -> CursorPage[WebhookEvent]: + params = WebhookEventListParams(limit=limit, page=page).to_dict() + response = self._client.transport.request( + "GET", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}/events", + params=params, + ) + return build_page( + payload=response.data, + item_factory=_coerce_webhook_event, + request_id=response.request_id, + fetch_next=lambda cursor: self.list_events( + webhook_id, + limit=limit, + page=cursor, + ), + ) + + +class AsyncWebhookEndpoints: + """Asynchronous Managed Agent webhook endpoint APIs.""" + + def __init__(self, client) -> None: + self._client = client + + async def create( + self, + *, + url: str, + events: Sequence[str], + description: Optional[str] = None, + ) -> WebhookEndpoint: + body = WebhookEndpointCreateParams( + url=url, + events=list(events), + description=description, + ).to_dict() + response = await self._client.transport.request( + "POST", + _PATH_WEBHOOK_ENDPOINTS, + json=body, + ) + return _coerce_webhook_endpoint(response.data) + + async def retrieve(self, webhook_id: str) -> WebhookEndpoint: + response = await self._client.transport.request( + "GET", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}", + ) + return _coerce_webhook_endpoint(response.data) + + get = retrieve + + async def list(self) -> WebhookEndpointList: + response = await self._client.transport.request( + "GET", + _PATH_WEBHOOK_ENDPOINTS, + ) + return WebhookEndpointList(**response.data) + + async def update( + self, + webhook_id: str, + *, + description: Optional[str] = None, + url: Optional[str] = None, + events: Optional[Sequence[str]] = None, + ) -> WebhookEndpoint: + body = WebhookEndpointUpdateParams( + description=description, + url=url, + events=list(events) if events is not None else None, + ).to_dict() + response = await self._client.transport.request( + "PUT", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}", + json=body, + ) + return _coerce_webhook_endpoint(response.data) + + async def delete(self, webhook_id: str) -> Dict[str, Any]: + response = await self._client.transport.request( + "DELETE", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}", + ) + return response.data + + async def enable(self, webhook_id: str) -> WebhookEndpoint: + response = await self._client.transport.request( + "POST", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}/enable", + ) + return _coerce_webhook_endpoint(response.data) + + async def disable(self, webhook_id: str) -> WebhookEndpoint: + response = await self._client.transport.request( + "POST", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}/disable", + ) + return _coerce_webhook_endpoint(response.data) + + async def test(self, webhook_id: str) -> WebhookEvent: + response = await self._client.transport.request( + "POST", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}/test", + ) + return _coerce_webhook_event(response.data) + + async def reset_secret(self, webhook_id: str) -> WebhookSecretReset: + response = await self._client.transport.request( + "POST", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}/reset_secret", + ) + return _coerce_webhook_secret_reset(response.data) + + async def list_events( + self, + webhook_id: str, + *, + limit: Optional[int] = None, + page: Optional[str] = None, + ) -> AsyncCursorPage[WebhookEvent]: + params = WebhookEventListParams(limit=limit, page=page).to_dict() + response = await self._client.transport.request( + "GET", + f"{_PATH_WEBHOOK_ENDPOINTS}/{webhook_id}/events", + params=params, + ) + + async def fetch_next(cursor: str) -> AsyncCursorPage[WebhookEvent]: + return await self.list_events( + webhook_id, + limit=limit, + page=cursor, + ) + + return build_page( + payload=response.data, + item_factory=_coerce_webhook_event, + request_id=response.request_id, + page_cls=AsyncCursorPage, + fetch_next=fetch_next, + ) diff --git a/dashscope/agentstudio/types/__init__.py b/dashscope/agentstudio/types/__init__.py index 497f2b7..65ec62a 100644 --- a/dashscope/agentstudio/types/__init__.py +++ b/dashscope/agentstudio/types/__init__.py @@ -79,6 +79,12 @@ SessionThreadStatusEvent, SpanModelRequestStartEvent, SpanModelRequestEndEvent, + WebhookDelivery, + WebhookEndpoint, + WebhookEndpointList, + WebhookEvent, + WebhookEventData, + WebhookSecretReset, ) from dashscope.agentstudio.types.params import ( # noqa: F401 AgentCreateParams, @@ -103,6 +109,9 @@ CredentialCreateParams, CredentialUpdateParams, CredentialListParams, + WebhookEndpointCreateParams, + WebhookEndpointUpdateParams, + WebhookEventListParams, DeploymentCreateParams, DeploymentListParams, DeploymentRunListParams, diff --git a/dashscope/agentstudio/types/models.py b/dashscope/agentstudio/types/models.py index 238cba1..29c8aec 100644 --- a/dashscope/agentstudio/types/models.py +++ b/dashscope/agentstudio/types/models.py @@ -541,6 +541,97 @@ class DeleteResponse(BaseModel): _fields = ("id", "type", "request_id") +class WebhookEndpoint(BaseModel): + """Managed Agent webhook endpoint.""" + + _fields = ( + "id", + "description", + "url", + "events", + "status", + "disabled_reason", + "consecutive_fail", + "last_success_at", + "last_failure_at", + "signing_secret", + "created_at", + "updated_at", + "request_id", + ) + + +class WebhookEndpointList(BaseModel): + """Non-paginated list of webhook endpoints in the current workspace.""" + + _fields = ("data", "request_id") + + def __init__(self, **kwargs: Any) -> None: + data = kwargs.get("data") + if isinstance(data, list): + kwargs["data"] = [ + WebhookEndpoint(**dict(item)) + if isinstance(item, Mapping) + else item + for item in data + ] + super().__init__(**kwargs) + + +class WebhookSecretReset(BaseModel): + """Result returned after resetting a webhook signing secret.""" + + _fields = ("id", "signing_secret", "updated_at", "request_id") + + +class WebhookEventData(BaseModel): + """Resource data carried by a webhook event envelope.""" + + _fields = ( + "id", + "type", + "workspace_id", + "session_thread_id", + "vault_id", + "extensions", + ) + + +class WebhookDelivery(BaseModel): + """Delivery information attached to an endpoint event.""" + + _fields = ( + "webhook_id", + "status", + "attempt_count", + "delivery_at", + "finish_at", + "failure_reason", + ) + + +class WebhookEvent(BaseModel): + """Webhook event envelope returned by test and endpoint event APIs.""" + + _fields = ( + "type", + "id", + "created_at", + "data", + "delivery", + "request_id", + ) + + def __init__(self, **kwargs: Any) -> None: + data = kwargs.get("data") + if isinstance(data, Mapping): + kwargs["data"] = WebhookEventData(**dict(data)) + delivery = kwargs.get("delivery") + if isinstance(delivery, Mapping): + kwargs["delivery"] = WebhookDelivery(**dict(delivery)) + super().__init__(**kwargs) + + class DeploymentAgentReference(BaseModel): """Agent version reference stored on a deployment run.""" diff --git a/dashscope/agentstudio/types/params.py b/dashscope/agentstudio/types/params.py index 4db590f..3621fff 100644 --- a/dashscope/agentstudio/types/params.py +++ b/dashscope/agentstudio/types/params.py @@ -752,3 +752,26 @@ def __init__( include_archived, ).lower() super().__init__(**kwargs) + + +# =========================================================================== +# Webhook endpoints +# =========================================================================== + + +class WebhookEndpointCreateParams(BaseModel): + """Request body for ``POST /webhook_endpoints``.""" + + _fields = ("description", "url", "events") + + +class WebhookEndpointUpdateParams(BaseModel): + """Request body for ``PUT /webhook_endpoints/{id}``.""" + + _fields = ("description", "url", "events") + + +class WebhookEventListParams(BaseModel): + """Query parameters for endpoint-specific webhook events.""" + + _fields = ("limit", "page")