ForcePilot/backend/package/yuxi/channel/extensions/intercom/status.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

188 lines
6.3 KiB
Python

import time
import logging
from yuxi.channel.extensions.intercom.client import IntercomClient
from yuxi.channel.extensions.intercom.types import IntercomProbeResult
from yuxi.channel.protocols import ChannelAccountSnapshot, ChannelStatusIssue
logger = logging.getLogger(__name__)
class IntercomStatus:
default_runtime: ChannelAccountSnapshot | None = None
async def probe(self, account: dict | None = None) -> bool:
if not account:
return False
access_token = account.get("access_token", "")
if not access_token:
return False
probe_result = await self._probe_intercom(access_token)
return probe_result.ok
async def _probe_intercom(self, access_token: str, timeout: float = 10.0) -> IntercomProbeResult:
client = IntercomClient(access_token, timeout=timeout)
start = time.monotonic()
try:
me = await client.get_me()
latency_ms = (time.monotonic() - start) * 1000
return IntercomProbeResult(
ok=bool(me.get("id")),
admin_id=str(me.get("id", "")),
app_id=str(me.get("app", {}).get("id_code", "")),
name=me.get("name", ""),
email=me.get("email", ""),
latency_ms=latency_ms,
)
except Exception as e:
latency_ms = (time.monotonic() - start) * 1000
return IntercomProbeResult(ok=False, error=str(e), latency_ms=latency_ms)
finally:
await client.close()
def build_summary(self, snapshot: object) -> dict:
return {
"channel": "intercom",
"account_id": getattr(snapshot, "account_id", ""),
"status": getattr(snapshot, "status_state", "unknown"),
}
def build_channel_summary(
self,
account: dict,
config: dict,
default_account_id: str,
snapshot: ChannelAccountSnapshot,
) -> dict:
account_id = account.get("account_id", "")
return {
"channel": "intercom",
"account_id": account_id,
"is_default": account_id == default_account_id,
"configured": snapshot.configured,
"enabled": snapshot.enabled,
"connected": snapshot.connected,
"running": snapshot.running,
"status": snapshot.status_state,
"health_state": snapshot.health_state,
"dm_policy": snapshot.dm_policy,
"last_error": snapshot.last_error,
"probe": snapshot.probe,
}
def build_account_snapshot(
self,
account: dict,
config: dict,
runtime: ChannelAccountSnapshot | None = None,
probe: object | None = None,
audit: object | None = None,
) -> ChannelAccountSnapshot:
from yuxi.channel.extensions.intercom.config import IntercomConfigAdapter
adapter = IntercomConfigAdapter()
account_id = account.get("account_id", "")
configured = adapter.is_configured(account)
enabled = adapter.is_enabled(account, config)
status_state = self.resolve_account_state(account, config, configured, enabled)
return ChannelAccountSnapshot(
account_id=account_id,
enabled=enabled,
configured=configured,
status_state=status_state,
dm_policy=account.get("dm_policy", "open"),
allow_from=account.get("allow_from", []),
probe=probe,
audit=audit,
)
def format_capabilities_probe(self, probe: object) -> list[dict]:
if probe is None:
return []
return [
{
"label": "Intercom 连接",
"ok": getattr(probe, "ok", False),
"detail": f"{getattr(probe, 'name', '')} / {getattr(probe, 'email', '')}",
"latency_ms": getattr(probe, "latency_ms", None),
}
]
async def audit_account(
self,
account: dict,
timeout_ms: int,
config: dict,
probe: object | None = None,
) -> object:
access_token = account.get("access_token", "")
if not access_token:
return {"ok": False, "error": "No access token configured"}
probe_result = await self._probe_intercom(access_token)
return {
"ok": probe_result.ok,
"admin_id": probe_result.admin_id,
"app_id": probe_result.app_id,
"name": probe_result.name,
"email": probe_result.email,
"latency_ms": probe_result.latency_ms,
"error": probe_result.error,
}
async def build_capabilities_diagnostics(
self,
account: dict,
timeout_ms: int,
config: dict,
probe: object | None = None,
audit: object | None = None,
target: str | None = None,
) -> dict | None:
return {
"channel": "intercom",
"probe": self.format_capabilities_probe(probe),
"audit": audit,
}
def log_self_id(
self,
account: dict,
config: dict,
runtime: object,
include_channel_prefix: bool = False,
) -> None:
prefix = "Intercom " if include_channel_prefix else ""
account_id = account.get("account_id", "default")
logger.info("%sBot account loaded: id=%s", prefix, account_id)
def resolve_account_state(
self,
account: dict,
config: dict,
configured: bool,
enabled: bool,
) -> str:
if not configured:
return "unconfigured"
if not enabled:
return "disabled"
return "enabled"
def collect_status_issues(self, accounts: list[ChannelAccountSnapshot]) -> list[ChannelStatusIssue]:
issues: list[ChannelStatusIssue] = []
for snapshot in accounts:
if snapshot.status_state == "unconfigured":
issues.append(
ChannelStatusIssue(
channel="intercom",
account_id=snapshot.account_id,
kind="unconfigured",
message=f"Intercom 账户 {snapshot.account_id} 未配置凭证",
fix="在管理面板中配置 Intercom Access Token",
)
)
return issues