通道适配器
LibreFang 通过 45 个通道适配器 连接各类消息平台,让用户可以在所有主流通信平台上与 Agent 交互。适配器覆盖消费级即时通讯、企业协作、社交媒体、社区平台、隐私优先协议、通用 Webhook 以及可扩展的 Sidecar 进程。
所有适配器共享统一的基础能力:通过 watch::channel 实现优雅关闭、连接失败时指数退避重试、使用 Zeroizing<String> 保护密钥、超出平台长度限制时自动拆分消息、支持按通道覆盖模型/提示词、DM/群组策略控制、按用户速率限制,以及输出格式化(Markdown、TelegramHTML、SlackMrkdwn、PlainText)。
目录
- 核心消息渠道 -- Telegram、Discord、Slack、WhatsApp、微信、Signal、Matrix、Email、WebChat
- 企业协作 -- Teams、Mattermost、Google Chat、Webex、飞书、Zulip、Flock、Twist、Pumble、Guilded
- 社交与社区 -- Reddit、Mastodon、Bluesky、Twitch、Gitter、Revolt、Keybase
- 集成与协议 -- LINE、Threema、钉钉、QQ、企业微信、IRC、XMPP、Ntfy、Gotify、Nextcloud、Mumble、Webhook、Voice
- 自定义适配器 -- 编写自定义适配器
通道配置
所有通道凭证支持四种存储方式(优先级从高到低):
- 系统环境变量:
export TELEGRAM_BOT_TOKEN=... - 加密金库(推荐):
librefang vault set TELEGRAM_BOT_TOKEN - .env 文件:
librefang config set-key telegram或直接编辑~/.librefang/.env - secrets.env: 通过仪表板 "Set API Key" 按钮写入
~/.librefang/secrets.env
以下示例展示全部四种方式。生产环境建议使用 vault。
大多数通道配置位于 ~/.librefang/config.toml 的 [channels] 段落下,每个内置通道是一个子段落。Telegram 现已改为 sidecar 模式,使用 [[sidecar_channels]] 声明——可选 Python(默认)或 Rust(自 #5831 起;见 Rust Telegram sidecar 适配器):
# Telegram via Python sidecar(默认)
[[sidecar_channels]]
name = "telegram"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.telegram"]
channel_type = "telegram"
[sidecar_channels.env]
TELEGRAM_BOT_TOKEN = "..."
# ALLOWED_USERS = "123456789,@alice"
# Telegram via Rust sidecar(自 #5831 起,不需要 Python 运行时)
# [[sidecar_channels]]
# name = "telegram"
# command = "/abs/path/to/target/release/librefang-sidecar-telegram"
# channel_type = "telegram"
# [sidecar_channels.env]
# TELEGRAM_BOT_TOKEN = "..."
# Discord (sidecar)
[[sidecar_channels]]
name = "discord"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.discord"]
channel_type = "discord"
[sidecar_channels.env]
DISCORD_BOT_TOKEN = "..."
# Slack (sidecar)
[[sidecar_channels]]
name = "slack"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.slack"]
channel_type = "slack"
[sidecar_channels.env]
SLACK_APP_TOKEN = "xapp-..."
SLACK_BOT_TOKEN = "xoxb-..."
# 企业示例(Teams — sidecar)
[[sidecar_channels]]
name = "teams"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.teams"]
channel_type = "teams"
[sidecar_channels.env]
TEAMS_APP_ID = "00000000-0000-0000-0000-000000000000"
TEAMS_WEBHOOK_PORT = "8459"
# 社交示例(Mastodon — sidecar)
[[sidecar_channels]]
name = "mastodon"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.mastodon"]
channel_type = "mastodon"
[sidecar_channels.env]
MASTODON_INSTANCE_URL = "https://mastodon.social"
通用字段
bot_token_env/token_env-- 保存 Bot/访问令牌的环境变量名。LibreFang 在启动时从该环境变量读取令牌。所有密钥以Zeroizing<String>存储,释放时自动清零。default_agent-- 当没有特定路由规则时,接收消息的 Agent 名称(或 ID)。allowed_users-- 可选的平台用户 ID 白名单。留空表示允许所有用户。overrides-- 可选的按通道行为覆盖(参见下方通道覆盖)。
环境变量参考(核心通道)
| 通道 | 必需的环境变量 |
|---|---|
| Telegram | TELEGRAM_BOT_TOKEN |
| Discord | DISCORD_BOT_TOKEN |
| Slack | SLACK_BOT_TOKEN, SLACK_APP_TOKEN |
WA_ACCESS_TOKEN, WA_PHONE_ID, WA_VERIFY_TOKEN | |
| Matrix | MATRIX_TOKEN |
EMAIL_PASSWORD |
其他通道的环境变量详见上方全部 45 个通道表格。
通道覆盖
ChannelOverrides 用来让 agent 按通道自定义行为。Sidecar 迁移后通道级这一层已经移除([channels.<name>.overrides] 子表挂在已删的进程内 ChannelsConfig 上),只剩 agent.toml 里的 agent 级 覆盖。原来通道级表里的大多数 knob 现在改到对应 sidecar 的 [sidecar_channels.env] 环境变量里 —— 详见频道配置指南里的迁移对照表。
- Agent 级 在
agent.toml—— 只对该 agent 在所有通道上生效。加顶层[channel_overrides]段。
解析顺序(迁移后):agent 级 → sidecar env → 内置默认。哪一层设了哪一层赢。
# agent.toml —— 每个 agent 自己的覆盖(比如 "这个 agent 永远回 DM")
[channel_overrides]
dm_policy = "always"
group_policy = "trigger_only"
group_trigger_patterns = ["(?i)\\bmy-bot\\b"]
# config.toml —— 通道级覆盖(历史 —— 已不再被识别)。
# #5463 之前,使用该通道的每个 agent 都继承下面这套默认。保留只为
# 迁移对照参考;新写法把等价值设在 agent 上(`agent.toml [channel_overrides]`)
# 或写到 sidecar 的 `[sidecar_channels.env]` 环境变量(如 `*_DM_POLICY`)。
[channels.discord.overrides]
model = "gemini-2.5-flash"
system_prompt = "You are a concise Discord assistant. Keep replies under 200 words."
dm_policy = "respond"
group_policy = "mention_only"
rate_limit_per_user = 10
threading = true
output_format = "markdown"
usage_footer = "compact"
覆盖字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
model | Option<String> | Agent 默认值 | 覆盖此通道使用的 LLM 模型。 |
system_prompt | Option<String> | Agent 默认值 | 覆盖此通道的系统提示词。 |
dm_policy | DmPolicy | Respond | 如何处理私信。 |
group_policy | GroupPolicy | MentionOnly | 如何处理群组/频道消息。 |
rate_limit_per_user | u32 | 0(无限制) | 每用户每分钟最大消息数。 |
threading | bool | false | 以线程回复形式发送响应(仅限支持的平台)。 |
output_format | Option<OutputFormat> | Markdown | 此通道的输出格式。 |
usage_footer | Option<UsageFooterMode> | 无 | 是否在响应末尾附加 token 用量信息。 |
格式化器、速率限制器与策略
输出格式化器
formatter 模块(librefang-channels/src/formatter.rs)将 LLM 输出的 Markdown 转换为平台原生格式:
| OutputFormat | 目标格式 | 说明 |
|---|---|---|
Markdown | 标准 Markdown | 默认值;原样传递。 |
TelegramHtml | Telegram HTML 子集 | 将 **bold** 转换为 <b>、`code` 转换为 <code> 等。 |
SlackMrkdwn | Slack mrkdwn | 将 **bold** 转换为 *bold*、链接转换为 <url|text> 等。 |
PlainText | 纯文本 | 去除所有格式。 |
按用户速率限制器
ChannelRateLimiter(librefang-channels/src/rate_limiter.rs)使用 DashMap 跟踪每用户消息计数。当通道覆盖中设置了 rate_limit_per_user 时,限制器以滑动窗口方式限制每分钟最多 N 条消息。超出限制的消息会收到友好的拒绝提示。
DM 策略
控制适配器如何处理私信:
| DmPolicy | 行为 |
|---|---|
Respond | 回复所有私信(默认)。 |
AllowedOnly | 仅回复 allowed_users 中用户的私信。 |
Ignore | 静默丢弃所有私信。 |
群组策略
控制适配器如何处理群聊、频道和房间中的消息:
| GroupPolicy | 行为 |
|---|---|
All | 回复群组中的所有消息。 |
MentionOnly | 仅在 Bot 被 @提及时回复(默认)。 |
CommandsOnly | 仅回复 /command 命令消息。 |
Ignore | 静默忽略所有群组消息。 |
策略在 dispatch_message() 中执行,早于消息到达 Agent 循环。这意味着被忽略的消息不会消耗任何 LLM token。
Agent 路由
AgentRouter 决定哪个 Agent 接收传入的消息。路由逻辑如下:
- 按通道默认值:每个通道配置都有一个
default_agent字段。来自该通道的消息会发送到该 Agent。 - 用户-Agent 绑定:如果用户之前已与特定 Agent 关联(通过命令或配置),来自该用户的消息会路由到该 Agent。
- 命令前缀:用户可以在聊天中发送类似
/agent coder的命令来切换 Agent。后续消息将路由到 "coder" Agent。 - 兜底:如果没有匹配的路由规则,消息会发送到第一个可用的 Agent。
当渠道配置了 default_agent 时,来自该渠道的消息会跳过语义路由和关键词路由,直接发送到指定的 Agent。用户仍可通过 /agent 命令手动切换 Agent。
出站标记
在 ChannelOverrides 里设 prefix_agent_name 给每条出站消息加 agent 名前缀。多个 agent 共享一个频道时有用——读者一眼能看出是哪个 agent 回的。
| 风格 | 包装效果 |
|---|---|
Off(默认) | 不变——跟 agent 文本字节相同 |
Bracket | [agent-name] 回复内容 |
BoldBracket | **[agent-name]** 回复内容(粗体渲染依赖通道的输出格式) |
把 prefix_agent_name 设到 agent 清单(agent.toml)的 [channel_overrides] 里;迁移前的 [channels.<name>.overrides] 位置已不再被识别。
# agent.toml
[channel_overrides]
prefix_agent_name = "BoldBracket"
包装在每个非流式成功路径上跑一次(auto_reply、kernel-streaming-with-status accumulated、streaming-fallback buffered_text、非流式 fallback、re-resolution 后重试、dispatch_with_blocks)。streaming tee(每个 chunk 到达就转发)不包装——前缀会跟 chunk 边界穿插。
Signal 默认纯文本
Signal 默认 OutputFormat::PlainText,原因是 signal-cli 会把 Markdown 的 * 和 _ 当字面量渲染,保留会产生可见噪音。这个映射写在 default_output_format_for_channel("signal") 中(见 crates/librefang-channels/src/formatter.rs),按 channel_type 字符串匹配;sidecar 迁移后 [[sidecar_channels]] 里仍然保留 channel_type = "signal",所以默认值依旧生效。
[[sidecar_channels]] 目前不暴露 output_format 的逐通道覆盖(原 overrides 块挂在 [channels.signal] 之类的进程内配置上,已随之一起删除)。如果某个 agent 必须原样发送 Markdown,请在 agent 这一端翻转 format。
其他通道保留各自的 formatter 默认值——只翻了 Signal。
反应表情与处理状态
几个 adapter 可以贴一个反应到用户消息表示"我在处理",发送回复时移除。各通道独立:
- Slack —
reactions_enabled开关(环境变量SLACK_REACTIONS,默认true)。收消息加 👀,回复时换成 ✅。already_reacted/no_reaction错误静默忽略(fail-open)。 - Feishu — 收消息加
Typingreaction,回复发送时移除。两个调用都 fire-and-forget;API 失败warn!但绝不阻塞消息处理。
要在自定义 adapter 上加同样能力,把 reaction 调用 tokio::spawn 出去,把 (reaction_id, message_id) 存在按 chat id 索引的 map 里。
Feishu @提及保留
Feishu 之前会静默删掉传入文本里的 @_user_N 占位符。Feishu adapter 现在用 mention payload 解析出的 @<display-name> 替换(name 缺失时回退 open_id),并把 @_all 重写为 @all。agent 看到原始对话语气——"@alice 你能 check 下吗?"——而不是裸标点。
Signal 媒体附件
已下线的进程内 Rust adapter 支持下载媒体 URL、base64 编码后塞进 POST /v2/send 的 base64_attachments,覆盖 Image、Voice、Video、Audio、Animation、File、FileData、MediaGroup。Sidecar 迁移(librefang.sidecar.adapters.signal)之后,非文本内容暂时统一退回 (Unsupported content type) 占位符;恢复 base64 round-trip 是后续工作。
WhatsApp DM / 群消息策略
迁移到 sidecar(librefang.sidecar.adapters.whatsapp)后,DM / 群组策略改通过 [[sidecar_channels]] 块上的环境变量配置:
WHATSAPP_DM_POLICY:respond(默认)/allowed_only/ignore。allowed_only与WHATSAPP_ALLOWED_USERS配合使用。WHATSAPP_GROUP_POLICY:all(默认)/mention_only/commands_only/ignore。mention_only模式下检查文本是否包含WHATSAPP_BOT_PHONE(带/不带@、去+)或WHATSAPP_BOT_NAME,大小写不敏感。
Cloud API 模式 出站支持 voice / image / file / location(通过标准 sidecar Content 变体)。Web/QR(Baileys)模式 把非文本内容降级为文本(voice URL → (Voice message: <url>)、无标题 image → (Image — not supported in Web mode)),对应原 Rust 适配器 whatsapp.rs:493-519 的 gateway-mode fallback。
Webhook deliver_only 模式
Webhook sidecar 可作为 pass-through 跑:传入 payload 完全绕过 LLM(无 sanitizer、无 rate limiter、无 agent 查找),直接转发到目标通道。适合只需要让消息落到 Telegram / Slack 等地方的推送通知和带外告警。
[[sidecar_channels]]
name = "webhook"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.webhook"]
channel_type = "webhook"
[sidecar_channels.env]
WEBHOOK_LISTEN_PORT = "8461"
WEBHOOK_DELIVER_ONLY = "1"
WEBHOOK_DELIVER = "telegram:123456789" # WEBHOOK_DELIVER_ONLY=1 时必填
WEBHOOK_SECRET 写入 ~/.librefang/secrets.env。WEBHOOK_DELIVER_ONLY=1 时若 WEBHOOK_DELIVER 为空,sidecar 启动时 fail-closed——原 Rust validator 同位置只 warn-and-continue,结果运行时入站被静默丢弃。扇出在内部用 __deliver_only__ / __deliver_target__ metadata 信号化,kernel bridge.rs 路由读这两个字段后短路 LLM 调用。
通道文件下载
Telegram(及其他通道)发的文件以临时认证 URL 形式过来,LLM 无法直接访问。bridge 现在下载到可配置目录,把消息重写成本地路径 content block——非图片文件变成 ContentBlock::Text 带保存路径(agent 调 file_read),图片变成 ContentBlock::ImageFile。
[channels]
file_download_dir = "/var/lib/librefang/channel-files" # 默认:~/.librefang/data/channel-files
file_download_max_bytes = 33554432 # 默认:32 MiB
启动 sweep 移除 24h 以上的过期文件;下载过程中还有概率 1/256 的额外 sweep,长跑的守护进程不会堆积孤儿附件。