2026-07-02 03:22:12 +08:00
|
|
|
|
"""飞书渠道插件入口。
|
|
|
|
|
|
|
|
|
|
|
|
实现 ``CHANNEL_ENTRY`` 契约,注册 18 个适配器与生命周期钩子,返回
|
|
|
|
|
|
``PluginManifest``。由宿主在 loaded 阶段通过 ``importlib`` 导入并调用。
|
|
|
|
|
|
|
|
|
|
|
|
调用顺序(严格):
|
2026-07-03 19:18:13 +08:00
|
|
|
|
1. ``getXxxPort`` 获取被驱动端口实例(宿主已从 manifest.accessible_ports
|
|
|
|
|
|
加载权限,无需运行时 declareXxx)。
|
|
|
|
|
|
2. 实例化 ``FeishuLifecycleHandler`` 与 ``FeishuClient``。
|
|
|
|
|
|
3. 实例化并注册 18 个适配器(17 个列表追加 + 1 个 identity_resolver 单实例)。
|
|
|
|
|
|
4. ``registerLifecycleHandler`` 注册生命周期钩子(鸭子类型调用)。
|
|
|
|
|
|
5. 返回 ``PluginManifest``。
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
|
|
|
|
|
per-account 适配器处理:``DirectoryAdapter`` / ``WhitelistAdapter`` /
|
|
|
|
|
|
``WizardAdapter`` 的 ``__init__`` 需 ``account_id``,但 ``CHANNEL_ENTRY``
|
|
|
|
|
|
在插件加载时不知具体账户,传入空字符串占位(``_PLACEHOLDER_ACCOUNT_ID``)。
|
|
|
|
|
|
实际 account_id 由 ``LifecycleAdapter.afterAccountConfigWritten`` 在账户配置
|
|
|
|
|
|
写入后按账户重建对应适配器,或由调用方通过 ``ConfigPort`` 动态读取。
|
|
|
|
|
|
|
|
|
|
|
|
WebSocket 长连接管理:飞书 WS 连接由 ``StreamWorker`` 通过
|
|
|
|
|
|
``FeishuStreamConnectorAdapter`` 统一管理(账号启用/禁用由
|
|
|
|
|
|
``ChannelAccountOnline``/``ChannelAccountOffline`` 领域事件触发),
|
|
|
|
|
|
``LifecycleAdapter`` 与 ``LifecycleHandler`` 不再自管理 WS 连接。
|
|
|
|
|
|
|
|
|
|
|
|
依赖方向:仅 import ``yuxi.channels.contract.*`` + 标准库 + 同插件内部模块,
|
|
|
|
|
|
不污染框架层。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from yuxi.channels.contract.dtos.capability import ChannelCapabilities
|
|
|
|
|
|
from yuxi.channels.contract.dtos.channel import ChannelType
|
|
|
|
|
|
from yuxi.channels.contract.dtos.config import ConfigField
|
|
|
|
|
|
from yuxi.channels.contract.dtos.plugin import EnvVar
|
2026-07-03 19:18:13 +08:00
|
|
|
|
from yuxi.channels.contract.plugin.entry import PluginHost
|
2026-07-02 03:22:12 +08:00
|
|
|
|
from yuxi.channels.contract.plugin.manifest import (
|
|
|
|
|
|
ChannelManifest,
|
|
|
|
|
|
FailurePolicy,
|
|
|
|
|
|
PluginManifest,
|
|
|
|
|
|
ResourceQuota,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
from .adapters.command_adapter import FeishuCommandAdapter
|
|
|
|
|
|
from .adapters.directory_adapter import FeishuDirectoryAdapter
|
|
|
|
|
|
from .adapters.doctor_adapter import FeishuDoctorAdapter
|
|
|
|
|
|
from .adapters.identity_resolver_adapter import FeishuIdentityResolverAdapter
|
|
|
|
|
|
from .adapters.inbound_adapter import FeishuInboundAdapter
|
|
|
|
|
|
from .adapters.lifecycle_adapter import FeishuLifecycleAdapter
|
|
|
|
|
|
from .adapters.login_adapter import FeishuLoginAdapter
|
|
|
|
|
|
from .adapters.mention_adapter import FeishuMentionAdapter
|
|
|
|
|
|
from .adapters.message_ops_adapter import FeishuMessageOpsAdapter
|
|
|
|
|
|
from .adapters.outbound_adapter import FeishuOutboundAdapter
|
|
|
|
|
|
from .adapters.probeable_adapter import FeishuProbeableAdapter
|
|
|
|
|
|
from .adapters.rich_message_adapter import FeishuRichMessageAdapter
|
|
|
|
|
|
from .adapters.session_adapter import FeishuSessionAdapter
|
|
|
|
|
|
from .adapters.status_adapter import FeishuStatusAdapter
|
|
|
|
|
|
from .adapters.stream_connector_adapter import FeishuStreamConnectorAdapter
|
|
|
|
|
|
from .adapters.streaming_adapter import FeishuStreamingAdapter
|
|
|
|
|
|
from .adapters.whitelist_adapter import FeishuWhitelistAdapter
|
|
|
|
|
|
from .adapters.wizard_adapter import FeishuWizardAdapter
|
|
|
|
|
|
from .feishu_client import FeishuClient
|
|
|
|
|
|
from .lifecycle import FeishuLifecycleHandler
|
|
|
|
|
|
|
|
|
|
|
|
# per-account 适配器占位 account_id:CHANNEL_ENTRY 在插件加载时不知具体账户,
|
|
|
|
|
|
# DirectoryAdapter / WhitelistAdapter / WizardAdapter 传入空字符串占位。
|
|
|
|
|
|
# 实际 account_id 由 LifecycleAdapter 在账户配置写入后按账户重建适配器实例,
|
|
|
|
|
|
# 或由适配器在方法调用时通过 ConfigPort 动态读取。
|
|
|
|
|
|
_PLACEHOLDER_ACCOUNT_ID = ""
|
|
|
|
|
|
|
|
|
|
|
|
# 可访问端口(与 manifest.json accessible_ports 一致)
|
|
|
|
|
|
_ACCESSIBLE_PORTS: tuple[str, ...] = (
|
|
|
|
|
|
"ConfigPort",
|
|
|
|
|
|
"LoggerPort",
|
|
|
|
|
|
"PersistencePort",
|
|
|
|
|
|
"CachePort",
|
|
|
|
|
|
"TracerPort",
|
|
|
|
|
|
"ConversationPort",
|
|
|
|
|
|
"IdentityResolverPort",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 可注入管道
|
|
|
|
|
|
_INJECTABLE_PIPELINES: tuple[str, ...] = ("inbound", "outbound")
|
|
|
|
|
|
|
|
|
|
|
|
# 已注册适配器类型列表(与 registerAdapter 调用顺序一致)
|
|
|
|
|
|
_ADAPTER_TYPES: tuple[str, ...] = (
|
2026-07-03 19:18:13 +08:00
|
|
|
|
"inbound",
|
|
|
|
|
|
"outbound",
|
|
|
|
|
|
"session",
|
|
|
|
|
|
"status",
|
|
|
|
|
|
"rich_message",
|
|
|
|
|
|
"streaming",
|
|
|
|
|
|
"mention",
|
|
|
|
|
|
"message_ops",
|
|
|
|
|
|
"directory",
|
|
|
|
|
|
"command",
|
|
|
|
|
|
"whitelist",
|
|
|
|
|
|
"wizard",
|
|
|
|
|
|
"doctor",
|
|
|
|
|
|
"lifecycle",
|
|
|
|
|
|
"probeable",
|
|
|
|
|
|
"login",
|
|
|
|
|
|
"identity_resolver",
|
|
|
|
|
|
"stream_connector",
|
2026-07-02 03:22:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def channel_entry(host: PluginHost) -> PluginManifest:
|
|
|
|
|
|
"""飞书渠道插件入口。
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
由宿主在 loaded 阶段调用,完成适配器注册、生命周期钩子注册,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
返回 ``PluginManifest``。
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
能力边界(accessible_ports / injectable_pipelines / resource_quota)
|
|
|
|
|
|
由 ``ChannelManifest`` 静态字段声明,宿主在加载期通过
|
|
|
|
|
|
``PluginCapabilityChecker.check`` 校验,运行时通过
|
|
|
|
|
|
``PluginHostImpl._checkPortAccess`` 从 manifest 读取并强制约束。
|
|
|
|
|
|
"""
|
2026-07-02 03:22:12 +08:00
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
# 1. 获取端口实例(宿主已从 manifest.accessible_ports 加载权限)
|
2026-07-02 03:22:12 +08:00
|
|
|
|
config_port = host.getConfigPort()
|
|
|
|
|
|
logger_port = host.getLoggerPort()
|
|
|
|
|
|
cache_port = host.getCachePort()
|
|
|
|
|
|
persistence_port = host.getPersistencePort()
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
# 2. 构造配置 schema(供 lifecycle handler 派生不可热更新键 + 复用至 manifest)
|
2026-07-02 03:22:12 +08:00
|
|
|
|
config_schema = _build_config_schema()
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
# 3. 实例化生命周期处理器(管理插件级 httpx 连接池)
|
2026-07-02 03:22:12 +08:00
|
|
|
|
handler = FeishuLifecycleHandler(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
config_port,
|
|
|
|
|
|
logger_port,
|
|
|
|
|
|
cache_port,
|
|
|
|
|
|
config_schema,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
# 4. 实例化 FeishuClient
|
2026-07-02 03:22:12 +08:00
|
|
|
|
client = FeishuClient(config_port, cache_port, logger_port)
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
# 5. 实例化并注册 18 个适配器
|
2026-07-02 03:22:12 +08:00
|
|
|
|
# 17 个列表追加 + 1 个 identity_resolver 单实例赋值
|
|
|
|
|
|
host.registerAdapter(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
"inbound",
|
|
|
|
|
|
FeishuInboundAdapter(client, config_port, logger_port),
|
2026-07-02 03:22:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
host.registerAdapter(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
"outbound",
|
|
|
|
|
|
FeishuOutboundAdapter(client, logger_port, cache_port),
|
2026-07-02 03:22:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
host.registerAdapter("session", FeishuSessionAdapter())
|
|
|
|
|
|
host.registerAdapter("status", FeishuStatusAdapter(logger_port))
|
|
|
|
|
|
host.registerAdapter("rich_message", FeishuRichMessageAdapter(logger_port))
|
|
|
|
|
|
host.registerAdapter(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
"streaming",
|
|
|
|
|
|
FeishuStreamingAdapter(client, logger_port, cache_port),
|
2026-07-02 03:22:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
host.registerAdapter("mention", FeishuMentionAdapter(config_port))
|
|
|
|
|
|
host.registerAdapter(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
"message_ops",
|
|
|
|
|
|
FeishuMessageOpsAdapter(client, logger_port, cache_port),
|
2026-07-02 03:22:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
host.registerAdapter(
|
|
|
|
|
|
"directory",
|
|
|
|
|
|
FeishuDirectoryAdapter(client, cache_port, _PLACEHOLDER_ACCOUNT_ID),
|
|
|
|
|
|
)
|
|
|
|
|
|
host.registerAdapter("command", FeishuCommandAdapter(config_port))
|
|
|
|
|
|
host.registerAdapter(
|
|
|
|
|
|
"whitelist",
|
|
|
|
|
|
FeishuWhitelistAdapter(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
persistence_port,
|
|
|
|
|
|
logger_port,
|
|
|
|
|
|
_PLACEHOLDER_ACCOUNT_ID,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
host.registerAdapter(
|
|
|
|
|
|
"wizard",
|
|
|
|
|
|
FeishuWizardAdapter(config_port, client, _PLACEHOLDER_ACCOUNT_ID),
|
|
|
|
|
|
)
|
|
|
|
|
|
host.registerAdapter("doctor", FeishuDoctorAdapter(client, config_port))
|
|
|
|
|
|
host.registerAdapter(
|
|
|
|
|
|
"lifecycle",
|
|
|
|
|
|
FeishuLifecycleAdapter(client, config_port, logger_port),
|
|
|
|
|
|
)
|
|
|
|
|
|
host.registerAdapter(
|
|
|
|
|
|
"probeable",
|
|
|
|
|
|
FeishuProbeableAdapter(client, config_port, persistence_port),
|
|
|
|
|
|
)
|
|
|
|
|
|
host.registerAdapter(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
"login",
|
|
|
|
|
|
FeishuLoginAdapter(client, config_port, logger_port),
|
2026-07-02 03:22:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
# identity_resolver 为单实例注册(非列表追加)
|
|
|
|
|
|
host.registerAdapter(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
"identity_resolver",
|
|
|
|
|
|
FeishuIdentityResolverAdapter(client, cache_port),
|
2026-07-02 03:22:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
# stream_connector:飞书 WS 长连接适配器,由 StreamWorker 管理连接生命周期。
|
|
|
|
|
|
# 需 ConfigPort 读取 ws_endpoint_path / api_base_url,支持按部署环境定制
|
|
|
|
|
|
# WS 端点 API 路径(私有化部署场景)。
|
|
|
|
|
|
host.registerAdapter(
|
|
|
|
|
|
"stream_connector",
|
|
|
|
|
|
FeishuStreamConnectorAdapter(persistence_port, logger_port, config_port),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
# 6. 注册生命周期钩子
|
2026-07-02 03:22:12 +08:00
|
|
|
|
# PluginHost Protocol 未声明 registerLifecycleHandler,但 PluginHostImpl
|
|
|
|
|
|
# 已实现,运行时通过鸭子类型调用。
|
|
|
|
|
|
host.registerLifecycleHandler(handler)
|
|
|
|
|
|
|
2026-07-03 19:18:13 +08:00
|
|
|
|
# 7. 返回 PluginManifest
|
2026-07-02 03:22:12 +08:00
|
|
|
|
return PluginManifest(
|
|
|
|
|
|
manifest=ChannelManifest(
|
|
|
|
|
|
id="com.yuxi.channels.feishu",
|
|
|
|
|
|
name="飞书",
|
|
|
|
|
|
version="1.0.0",
|
2026-07-04 00:14:56 +08:00
|
|
|
|
channel_type=ChannelType("feishu"),
|
2026-07-02 03:22:12 +08:00
|
|
|
|
provides=("feishu",),
|
|
|
|
|
|
entry_module="yuxi.channels.plugins.feishu.entry",
|
|
|
|
|
|
capabilities=ChannelCapabilities(
|
|
|
|
|
|
rich_message=True,
|
|
|
|
|
|
streaming=True,
|
|
|
|
|
|
typing_indicator=True,
|
|
|
|
|
|
message_edit=True,
|
|
|
|
|
|
message_recall=True,
|
|
|
|
|
|
supports_reaction=True,
|
|
|
|
|
|
supports_pin=True,
|
|
|
|
|
|
supports_card_update=True,
|
2026-07-03 19:18:13 +08:00
|
|
|
|
supports_card_update_streaming=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
supports_image_inbound=True,
|
|
|
|
|
|
supports_video_inbound=False,
|
|
|
|
|
|
supports_image_outbound=True,
|
|
|
|
|
|
supports_video_outbound=True,
|
2026-07-03 19:18:13 +08:00
|
|
|
|
mention=True,
|
|
|
|
|
|
command=True,
|
|
|
|
|
|
directory=True,
|
|
|
|
|
|
doctor=True,
|
|
|
|
|
|
whitelist=True,
|
|
|
|
|
|
wizard=False,
|
|
|
|
|
|
tools=False,
|
|
|
|
|
|
status=False,
|
|
|
|
|
|
probeable=False,
|
|
|
|
|
|
identity_resolver=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
lifecycle=True,
|
|
|
|
|
|
supports_qr_login=True,
|
|
|
|
|
|
),
|
|
|
|
|
|
config_schema=config_schema,
|
|
|
|
|
|
lifecycle=("init", "start", "stop", "unload"),
|
|
|
|
|
|
compatibility={
|
|
|
|
|
|
"min_host_version": "1.0.0",
|
|
|
|
|
|
"feishu_api_version": "v2",
|
|
|
|
|
|
},
|
|
|
|
|
|
failure_policy=FailurePolicy.DEGRADE,
|
|
|
|
|
|
resource_quota=ResourceQuota(
|
|
|
|
|
|
max_cpu="20.0",
|
|
|
|
|
|
max_memory="256MB",
|
|
|
|
|
|
max_connections=50,
|
|
|
|
|
|
max_calls_per_sec=50,
|
|
|
|
|
|
),
|
|
|
|
|
|
accessible_ports=_ACCESSIBLE_PORTS,
|
|
|
|
|
|
injectable_pipelines=_INJECTABLE_PIPELINES,
|
|
|
|
|
|
env_vars=_build_env_vars(),
|
|
|
|
|
|
critical=False,
|
|
|
|
|
|
requires_dm_pairing=True,
|
|
|
|
|
|
requires_outbound_delivery=True,
|
2026-07-03 19:18:13 +08:00
|
|
|
|
max_message_length=30720,
|
|
|
|
|
|
supports_credential_cloning=False,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
),
|
|
|
|
|
|
adapters=_ADAPTER_TYPES,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _build_config_schema() -> tuple[ConfigField, ...]:
|
|
|
|
|
|
"""构造 18 个 ConfigField(与 manifest.json config_schema 一致)。"""
|
|
|
|
|
|
return (
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="app_id",
|
|
|
|
|
|
type="string",
|
|
|
|
|
|
required=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
hot_reloadable=False,
|
|
|
|
|
|
constraints={"pattern": "^cli_[a-zA-Z0-9]+$"},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="app_secret",
|
|
|
|
|
|
type="string",
|
|
|
|
|
|
required=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
hot_reloadable=False,
|
|
|
|
|
|
constraints={"sensitive": True},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="encrypt_key",
|
|
|
|
|
|
type="string",
|
|
|
|
|
|
required=False,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
hot_reloadable=True,
|
|
|
|
|
|
constraints={"sensitive": True},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="verification_token",
|
|
|
|
|
|
type="string",
|
|
|
|
|
|
required=False,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
hot_reloadable=True,
|
|
|
|
|
|
constraints={"sensitive": True},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="event_mode",
|
|
|
|
|
|
type="enum",
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
default="websocket",
|
|
|
|
|
|
hot_reloadable=False,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
constraints={"values": ["websocket", "webhook"]},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="webhook_url",
|
|
|
|
|
|
type="string",
|
|
|
|
|
|
required=False,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
hot_reloadable=False,
|
|
|
|
|
|
constraints={"pattern": "^https://"},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="bot_name",
|
|
|
|
|
|
type="string",
|
|
|
|
|
|
required=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
hot_reloadable=True,
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="bot_open_id",
|
|
|
|
|
|
type="string",
|
|
|
|
|
|
required=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
hot_reloadable=True,
|
|
|
|
|
|
constraints={"pattern": "^ou_[a-zA-Z0-9]+$"},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="enable_qr_login",
|
|
|
|
|
|
type="boolean",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default=False,
|
|
|
|
|
|
hot_reloadable=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="qr_redirect_uri",
|
|
|
|
|
|
type="string",
|
|
|
|
|
|
required=False,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
hot_reloadable=True,
|
|
|
|
|
|
constraints={"pattern": "^https://"},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="qr_timeout_ms",
|
|
|
|
|
|
type="integer",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default=120000,
|
|
|
|
|
|
hot_reloadable=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
constraints={"min": 30000, "max": 300000},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="api_base_url",
|
|
|
|
|
|
type="string",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default="https://open.feishu.cn",
|
|
|
|
|
|
hot_reloadable=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="ws_endpoint",
|
|
|
|
|
|
type="string",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default="wss://open.feishu.cn",
|
|
|
|
|
|
hot_reloadable=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="request_timeout_ms",
|
|
|
|
|
|
type="integer",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default=30000,
|
|
|
|
|
|
hot_reloadable=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
constraints={"min": 5000, "max": 60000},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="max_retries",
|
|
|
|
|
|
type="integer",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default=3,
|
|
|
|
|
|
hot_reloadable=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
constraints={"min": 0, "max": 5},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="rate_limit_per_sec",
|
|
|
|
|
|
type="integer",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default=50,
|
|
|
|
|
|
hot_reloadable=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
constraints={"min": 1, "max": 100},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="user_cache_ttl_sec",
|
|
|
|
|
|
type="integer",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default=1800,
|
|
|
|
|
|
hot_reloadable=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
constraints={"min": 60, "max": 7200},
|
|
|
|
|
|
),
|
|
|
|
|
|
ConfigField(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
key="identity_cache_ttl_sec",
|
|
|
|
|
|
type="integer",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default=60,
|
|
|
|
|
|
hot_reloadable=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
constraints={"min": 30, "max": 600},
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _build_env_vars() -> tuple[EnvVar, ...]:
|
|
|
|
|
|
"""构造 7 个 EnvVar(与 manifest.json env_vars 一致)。"""
|
|
|
|
|
|
return (
|
|
|
|
|
|
EnvVar(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
name="FEISHU_APP_ID",
|
|
|
|
|
|
description="飞书应用 App ID",
|
2026-07-02 03:22:12 +08:00
|
|
|
|
required=True,
|
|
|
|
|
|
),
|
|
|
|
|
|
EnvVar(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
name="FEISHU_APP_SECRET",
|
|
|
|
|
|
description="飞书应用 App Secret",
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
sensitive=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
),
|
|
|
|
|
|
EnvVar(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
name="FEISHU_ENCRYPT_KEY",
|
|
|
|
|
|
description="事件加密密钥",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
sensitive=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
),
|
|
|
|
|
|
EnvVar(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
name="FEISHU_VERIFICATION_TOKEN",
|
|
|
|
|
|
description="Webhook 验证 Token",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
sensitive=True,
|
2026-07-02 03:22:12 +08:00
|
|
|
|
),
|
|
|
|
|
|
EnvVar(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
name="FEISHU_BOT_NAME",
|
|
|
|
|
|
description="飞书机器人名称",
|
2026-07-02 03:22:12 +08:00
|
|
|
|
required=True,
|
|
|
|
|
|
),
|
|
|
|
|
|
EnvVar(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
name="FEISHU_QR_REDIRECT_URI",
|
|
|
|
|
|
description="扫码登录回调 URI",
|
2026-07-02 03:22:12 +08:00
|
|
|
|
required=False,
|
|
|
|
|
|
),
|
|
|
|
|
|
EnvVar(
|
2026-07-03 19:18:13 +08:00
|
|
|
|
name="FEISHU_QR_TIMEOUT_MS",
|
|
|
|
|
|
description="扫码登录超时时间(毫秒)",
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
default="120000",
|
2026-07-02 03:22:12 +08:00
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CHANNEL_ENTRY = channel_entry
|