"""Zendesk 厂商集成元数据。 定义 ``IntegrationMetadata(key="zendesk", ...)``,由 ``__init__.py`` 自注册到 ``IntegrationRegistry``。元数据为内存态,不持久化到数据库。 同时提供: - ``ZendeskConnectionConfig``:Zendesk 连接配置 Schema(含 ``subdomain`` / ``base_url``), 供前端动态表单与校验。 - ``resolve_oauth_endpoints``:根据 ``base_url`` 拼接 Zendesk 实例级 OAuth 端点, 供 handler 在 OAuth2 模式下注入到 ``auth_config``。 认证设计(见设计文档 §3): - ``oauth2_authorization_code``:复用框架级插件,handler 内通过 ``resolve_oauth_endpoints`` 注入 ``token_url``。 - ``basic``(API Token 模式):复用框架级 ``BasicAuthPlugin``, ``username="{email}/token"``,``password=api_token``。 """ from __future__ import annotations from pydantic import BaseModel, Field, model_validator from yuxi.external_systems.integrations.schemas import IntegrationMetadata ZENDESK_METADATA = IntegrationMetadata( key="zendesk", display_name="Zendesk", description="客服 / 工单平台,基于 Support REST API 集成", adapter_type="http", source_types=["zendesk"], supported_auth_types=[ "oauth2_authorization_code", # 标准 OAuth2 "basic", # API Token 模式(username="{email}/token", password=api_token) ], tags=["customer_support", "ticketing", "crm"], icon="zendesk", docs_url="https://developer.zendesk.com/api-reference/", connection_extra_schema={ "type": "object", "properties": { "subdomain": {"type": "string", "description": "Zendesk 子域名,如 acme"}, "base_url": { "type": "string", "description": "完整 base_url(优先级高于 subdomain)", }, }, }, example_config={ "connection_config": { "subdomain": "acme", }, "auth_config": { "auth_type": "basic", "username": "agent@acme.com/token", "password": "ref://zendesk/acme/api_token", }, }, dependency_note=( "1. 调用账号需对 tickets / users / organizations / ticket_fields / " "user_fields / organization_fields / search 具备 READ 权限;" "2. OAuth2 应用需在 Zendesk 后台注册并配置回调地址;" "3. API Token 在 Zendesk 后台 Admin > Channels > API 中生成," "auth_type=basic,username='{email}/token',password=api_token;" "4. api_token 建议使用 ref:// 密钥引用;" "5. 通用 API 限流 OAuth2 700/min、basic 200/min,搜索 API 30/min。" ), ) class ZendeskConnectionConfig(BaseModel): """Zendesk 连接配置 Schema(供前端动态表单与校验)。 ``base_url`` 优先级高于 ``subdomain``,支持自定义域名场景。两者至少填写一项, 仅填写 ``subdomain`` 时自动拼接为 ``https://{subdomain}.zendesk.com``。 """ subdomain: str | None = Field( default=None, description="Zendesk 子域名,如 acme(对应 https://acme.zendesk.com)", ) base_url: str | None = Field( default=None, description="完整 base_url,优先级高于 subdomain;用于自定义域名或私有部署", ) @model_validator(mode="after") def _ensure_url(self) -> ZendeskConnectionConfig: if not self.base_url and not self.subdomain: raise ValueError("subdomain 与 base_url 至少填写一项") if not self.base_url: self.base_url = f"https://{self.subdomain}.zendesk.com" return self def resolve_oauth_endpoints(base_url: str) -> dict[str, str]: """根据 base_url 拼接 Zendesk 实例级 OAuth 端点。 Args: base_url: Zendesk 实例 base_url(如 ``https://acme.zendesk.com``)。 Returns: 含 ``authorize_url`` / ``token_url`` 的字典,供 handler 注入到 ``auth_config`` 后传给框架级 OAuth2 插件消费。 """ return { "authorize_url": f"{base_url}/oauth/authorizations/new", "token_url": f"{base_url}/oauth/tokens", }