ForcePilot/backend/package/yuxi/channel/extensions/intercom/config.py
Kris 2ddcf1cc55 feat(channel): 添加 Intercom 渠道扩展
新增 Intercom 渠道扩展,支持在 Yuxi 平台中集成 Intercom 客服渠道。

包含以下功能模块:
- client: Intercom API 客户端封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- streaming: 流式消息处理
- format: 消息格式转换
- outbound: 外发消息管理
- handoff: 转人工会话切换
- pairing: 用户配对与绑定
- security: 安全签名校验
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- session_guard: 会话防护
- types: 类型定义
2026-05-21 11:01:49 +08:00

193 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import logging
from yuxi.channel.extensions.intercom.types import IntercomAccount
logger = logging.getLogger(__name__)
class IntercomConfigAdapter:
def list_account_ids(self, config: dict) -> list[str]:
accounts = config.get("channels", {}).get("intercom", {}).get("accounts", {})
if accounts:
return list(accounts.keys())
if os.getenv("INTERCOM_ACCESS_TOKEN"):
return ["default"]
return []
async def resolve_account(self, account_id: str, config: dict | None = None) -> dict:
account = self._resolve_account_dataclass(account_id, config or {})
return {
"account_id": account.account_id,
"access_token": account.access_token,
"webhook_secret": account.webhook_secret,
"admin_id": account.admin_id,
"dm_policy": account.dm_policy,
"allow_from": account.allow_from,
"auto_skip_closed": account.auto_skip_closed,
"auto_skip_snoozed": account.auto_skip_snoozed,
"handoff_policy": account.handoff_policy,
"datacenter": account.datacenter,
}
def _resolve_account_dataclass(self, account_id: str, config: dict) -> IntercomAccount:
channel_cfg = config.get("channels", {}).get("intercom", {})
accounts = channel_cfg.get("accounts", {})
account_cfg = accounts.get(account_id, {}) if accounts else channel_cfg
access_token = self._resolve_token(account_cfg.get("accessToken"), "INTERCOM_ACCESS_TOKEN")
webhook_secret = self._resolve_token(account_cfg.get("webhookSecret"), "INTERCOM_WEBHOOK_SECRET")
admin_id = account_cfg.get("adminId") or os.getenv("INTERCOM_ADMIN_ID", "")
return IntercomAccount(
account_id=account_id,
access_token=access_token,
webhook_secret=webhook_secret,
admin_id=admin_id,
dm_policy=account_cfg.get("dmPolicy", "open"),
allow_from=account_cfg.get("allowFrom", []),
auto_skip_closed=account_cfg.get("autoSkipClosed", True),
auto_skip_snoozed=account_cfg.get("autoSkipSnoozed", True),
handoff_policy=account_cfg.get("handoffPolicy", "ignore"),
datacenter=account_cfg.get("datacenter", "us"),
)
@staticmethod
def _resolve_token(account_value: str | None, env_key: str) -> str:
if account_value is not None:
if account_value == "none":
return ""
if account_value.startswith("$"):
env_val = os.environ.get(account_value.strip("${}"), "")
return env_val
return account_value
return os.environ.get(env_key, "")
def is_configured(self, account: dict) -> bool:
return bool(account.get("access_token"))
def is_enabled(self, account: dict, config: dict) -> bool:
return True
def disabled_reason(self, account: dict, config: dict) -> str:
return ""
def unconfigured_reason(self, account: dict, config: dict) -> str:
if self.is_configured(account):
return ""
return "Missing required credential: accessToken"
def describe_account(self, account: dict, config: dict) -> dict:
return {
"account_id": account.get("account_id", ""),
"dm_policy": account.get("dm_policy", "open"),
"handoff_policy": account.get("handoff_policy", "ignore"),
}
def default_account_id(self, config: dict) -> str:
return config.get("channels", {}).get("intercom", {}).get("default_account", "default")
def resolve_allow_from(self, config: dict, account_id: str | None = None) -> list[str] | None:
channel_cfg = config.get("channels", {}).get("intercom", {})
accounts = channel_cfg.get("accounts", {})
if account_id and account_id in accounts:
return accounts[account_id].get("allowFrom")
return channel_cfg.get("allowFrom")
def format_allow_from(self, config: dict, account_id: str | None, allow_from: list[str | int]) -> list[str]:
return [str(entry) for entry in allow_from]
def has_configured_state(self, config: dict) -> bool:
accounts = config.get("channels", {}).get("intercom", {}).get("accounts", {})
if accounts:
for account_cfg in accounts.values():
if account_cfg.get("accessToken") or account_cfg.get("accessToken") is None:
return True
return bool(os.getenv("INTERCOM_ACCESS_TOKEN"))
def has_persisted_auth_state(self, config: dict) -> bool:
accounts = config.get("channels", {}).get("intercom", {}).get("accounts", {})
if accounts:
for account_cfg in accounts.values():
if account_cfg.get("accessToken"):
return True
return False
def config_schema(self) -> dict:
return {
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Intercom 渠道配置",
"properties": {
"accounts": {
"type": "object",
"title": "账户列表",
"description": "Intercom 账户配置key 为账户 ID",
"additionalProperties": {
"type": "object",
"properties": {
"accessToken": {
"type": "string",
"title": "Access Token",
"description": "Intercom Access Token (Settings > Developer Hub > Access Tokens)",
"x-ui-password": True,
},
"webhookSecret": {
"type": "string",
"title": "Webhook Secret",
"description": "用于验证 Webhook 请求的 HMAC 密钥",
"x-ui-password": True,
},
"adminId": {
"type": "string",
"title": "Admin ID",
"description": "Bot 的管理员 ID留空则自动从 API 获取)",
},
"dmPolicy": {
"type": "string",
"title": "DM 安全策略",
"enum": ["open", "pairing", "allowlist", "disabled"],
"default": "open",
},
"allowFrom": {
"type": "array",
"title": "白名单 Contact ID",
"items": {"type": "string"},
"description": "仅 dmPolicy=allowlist 时生效,格式: contact_xxx 或 lead_xxx",
},
"autoSkipClosed": {
"type": "boolean",
"title": "跳过已关闭会话",
"default": True,
},
"autoSkipSnoozed": {
"type": "boolean",
"title": "跳过已暂停会话",
"default": True,
},
"handoffPolicy": {
"type": "string",
"title": "人工接管策略",
"enum": ["ignore", "note_only", "continue"],
"default": "ignore",
"description": "检测到人工接管后的行为",
},
"datacenter": {
"type": "string",
"title": "数据中心",
"enum": ["us", "eu", "au"],
"default": "us",
"description": "Intercom 数据中心区域",
},
},
"required": ["accessToken"],
},
},
"default_account": {
"type": "string",
"title": "默认账户",
"default": "default",
},
},
}