ForcePilot/backend/package/yuxi/channels/adapters/feishu/config_schema.py
Kris a6fa7245e5 feat(feishu): 完整实现飞书适配器核心模块
新增飞书机器人适配器全套功能,包括:
- 基础适配器入口与工具导出
- 消息格式化、卡片渲染、回复调度逻辑
- 会话ID生成、模型覆盖策略
- 消息发送缓存、顺序队列管理
- 飞书签名验证、加解密webhook请求
- 审批权限校验、机器人菜单事件处理
- 文档评论、钉消息、语音转码处理
- 静态/动态目录管理、子代理生命周期管理
- 各类工具集:聊天、云盘、文档、知识库API封装
2026-05-12 00:43:59 +08:00

101 lines
4.0 KiB
Python

from __future__ import annotations
from typing import Any
from yuxi.utils.logging_config import logger
def super_refine_feishu_config(config: dict[str, Any]) -> dict[str, Any]:
refined = dict(config)
group_policy = refined.get("group_policy", refined.get("groupPolicy", "allowlist"))
if group_policy == "allowall":
logger.info("[FeishuConfig] Converting legacy 'allowall' to 'open'")
refined["groupPolicy"] = "open"
refined["group_policy"] = "open"
default_account = refined.get("defaultAccount", "")
if default_account:
accounts = refined.get("accounts", {})
if isinstance(accounts, dict) and default_account not in accounts:
logger.warning(
"[FeishuConfig] defaultAccount '%s' not found in accounts, clearing",
default_account,
)
refined.pop("defaultAccount", None)
verify_token = refined.get("verify_token", refined.get("verifyToken", ""))
encrypt_key = refined.get("encrypt_key", refined.get("encryptKey", ""))
webhook_path = refined.get("webhookPath", "")
if webhook_path:
if not verify_token and not encrypt_key:
logger.warning("[FeishuConfig] webhook mode configured but no verifyToken/encryptKey set")
allowlist = refined.get("allowlist", refined.get("allowFrom", []))
dm_policy = refined.get("dm_policy", refined.get("dmPolicy", "pairing"))
if dm_policy == "open" and ("*" in allowlist):
logger.info("[FeishuConfig] DM policy is 'open', wildcard in allowlist is redundant")
accounts = refined.get("accounts", {})
if isinstance(accounts, dict):
for acct_name, acct_cfg in accounts.items():
if not isinstance(acct_cfg, dict):
continue
for field in ("appId", "appSecret", "verifyToken", "encryptKey", "domain", "platform"):
if field not in acct_cfg:
top_key = _camel_to_snake(field)
if top_key in refined:
acct_cfg[field] = refined[top_key]
return refined
def _camel_to_snake(name: str) -> str:
result = []
for ch in name:
if ch.isupper():
result.append("_")
result.append(ch.lower())
else:
result.append(ch)
return "".join(result).lstrip("_")
def validate_feishu_config(config: dict[str, Any]) -> list[str]:
errors: list[str] = []
app_id = config.get("app_id", config.get("appId", ""))
app_secret = config.get("app_secret", config.get("appSecret", ""))
if not app_id:
errors.append("app_id / appId 为必填项")
if not app_secret:
errors.append("app_secret / appSecret 为必填项")
group_policy = config.get("group_policy", config.get("groupPolicy", "allowlist"))
valid_policies = {"open", "allowlist", "disabled", "allowall"}
if group_policy not in valid_policies:
errors.append(f"groupPolicy 值无效: '{group_policy}',有效值为: {valid_policies}")
dm_policy = config.get("dm_policy", config.get("dmPolicy", "pairing"))
valid_dm_policies = {"open", "pairing", "allowlist"}
if dm_policy not in valid_dm_policies:
errors.append(f"dmPolicy 值无效: '{dm_policy}',有效值为: {valid_dm_policies}")
accounts = config.get("accounts", {})
if isinstance(accounts, dict):
for name, acct_cfg in accounts.items():
if not isinstance(acct_cfg, dict):
continue
acct_app_id = acct_cfg.get("appId", acct_cfg.get("app_id", ""))
acct_app_secret = acct_cfg.get("appSecret", acct_cfg.get("app_secret", ""))
if not acct_app_id or not acct_app_secret:
errors.append(f"账户 '{name}' 缺少 appId 或 appSecret")
max_media = config.get("max_media_size_mb", config.get("mediaMaxMb", 50))
if not isinstance(max_media, (int, float)) or max_media <= 0 or max_media > 200:
errors.append(f"mediaMaxMb 值无效: {max_media},有效范围: 1-200")
return errors