通道适配器

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、飞书、Rocket.Chat、Zulip、Flock、Twist、Pumble、Guilded
  • 社交与社区 -- Reddit、Mastodon、Bluesky、LinkedIn、Twitch、Discourse、Gitter、Revolt、Keybase、Nostr
  • 集成与协议 -- LINE、Viber、Messenger、Threema、钉钉、QQ、企业微信、IRC、XMPP、MQTT、Ntfy、Gotify、Nextcloud、Mumble、Webhook、Voice
  • 自定义适配器 -- 编写自定义适配器

通道配置

所有通道配置位于 ~/.librefang/config.toml[channels] 段落下。每个通道是一个子段落:

[channels.telegram]
bot_token_env = "TELEGRAM_BOT_TOKEN"
default_agent = "assistant"
allowed_users = ["123456789"]

[channels.discord]
bot_token_env = "DISCORD_BOT_TOKEN"
default_agent = "coder"

[channels.slack]
bot_token_env = "SLACK_BOT_TOKEN"
app_token_env = "SLACK_APP_TOKEN"
default_agent = "ops"

# 企业示例
[channels.teams]
app_id_env = "TEAMS_APP_ID"
app_secret_env = "TEAMS_APP_SECRET"
default_agent = "ops"

# 社交示例
[channels.mastodon]
token_env = "MASTODON_TOKEN"
instance = "https://mastodon.social"
default_agent = "social-media"

# IoT/MQTT 示例(需要 all-channels feature flag)
[channels.mqtt]
host = "broker.example.com"
port = 1883
client_id = "librefang-bot"
subscribe_topics = ["home/agents/input", "home/agents/commands"]
response_topic = "home/agents/output"
qos = "at_least_once"                # at_most_once | at_least_once | exactly_once
# username_env = "MQTT_USERNAME"     # 可选
# password_env = "MQTT_PASSWORD"     # 可选
keep_alive_secs = 30
default_agent = "home-assistant"

通用字段

  • bot_token_env / token_env -- 保存 Bot/访问令牌的环境变量名。LibreFang 在启动时从该环境变量读取令牌。所有密钥以 Zeroizing<String> 存储,释放时自动清零。
  • default_agent -- 当没有特定路由规则时,接收消息的 Agent 名称(或 ID)。
  • allowed_users -- 可选的平台用户 ID 白名单。留空表示允许所有用户。
  • overrides -- 可选的按通道行为覆盖(参见下方通道覆盖)。

环境变量参考(核心通道)

通道必需的环境变量
TelegramTELEGRAM_BOT_TOKEN
DiscordDISCORD_BOT_TOKEN
SlackSLACK_BOT_TOKEN, SLACK_APP_TOKEN
WhatsAppWA_ACCESS_TOKEN, WA_PHONE_ID, WA_VERIFY_TOKEN
MatrixMATRIX_TOKEN
EmailEMAIL_PASSWORD

其他通道的环境变量详见上方全部 45 个通道表格。


通道覆盖

每个通道适配器都支持 ChannelOverrides,可在两个层级自定义按通道行为:

  • 通道级config.toml —— 对使用该通道的所有 agent 生效。加 [channels.<name>.overrides] 段。
  • Agent 级agent.toml —— 只对该 agent 在所有通道上生效。加顶层 [channel_overrides] 段。

解析顺序:agent 级 → 通道级 → 内置默认。哪一层设了哪一层赢;没设的字段穿透到下一层(跟 exec_policy 一样的模式)。

# agent.toml —— 每个 agent 自己的覆盖(比如 "这个 agent 永远回 DM")
[channel_overrides]
dm_policy = "always"
group_policy = "trigger_only"
group_trigger_patterns = ["(?i)\\bmy-bot\\b"]
# config.toml —— 该通道上对所有 agent 的默认
[channels.telegram.overrides]
model = "gemini-2.5-flash"
system_prompt = "You are a concise Telegram assistant. Keep replies under 200 words."
dm_policy = "respond"
group_policy = "mention_only"
rate_limit_per_user = 10
threading = true
output_format = "telegram_html"
usage_footer = "compact"

覆盖字段

字段类型默认值说明
modelOption<String>Agent 默认值覆盖此通道使用的 LLM 模型。
system_promptOption<String>Agent 默认值覆盖此通道的系统提示词。
dm_policyDmPolicyRespond如何处理私信。
group_policyGroupPolicyMentionOnly如何处理群组/频道消息。
rate_limit_per_useru320(无限制)每用户每分钟最大消息数。
threadingboolfalse以线程回复形式发送响应(仅限支持的平台)。
output_formatOption<OutputFormat>Markdown此通道的输出格式。
usage_footerOption<UsageFooterMode>是否在响应末尾附加 token 用量信息。

格式化器、速率限制器与策略

输出格式化器

formatter 模块(librefang-channels/src/formatter.rs)将 LLM 输出的 Markdown 转换为平台原生格式:

OutputFormat目标格式说明
Markdown标准 Markdown默认值;原样传递。
TelegramHtmlTelegram HTML 子集**bold** 转换为 <b>`code` 转换为 <code> 等。
SlackMrkdwnSlack mrkdwn**bold** 转换为 *bold*、链接转换为 <url|text> 等。
PlainText纯文本去除所有格式。

按用户速率限制器

ChannelRateLimiterlibrefang-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 接收传入的消息。路由逻辑如下:

  1. 按通道默认值:每个通道配置都有一个 default_agent 字段。来自该通道的消息会发送到该 Agent。
  2. 用户-Agent 绑定:如果用户之前已与特定 Agent 关联(通过命令或配置),来自该用户的消息会路由到该 Agent。
  3. 命令前缀:用户可以在聊天中发送类似 /agent coder 的命令来切换 Agent。后续消息将路由到 "coder" Agent。
  4. 兜底:如果没有匹配的路由规则,消息会发送到第一个可用的 Agent。

当渠道配置了 default_agent 时,来自该渠道的消息会跳过语义路由和关键词路由,直接发送到指定的 Agent。用户仍可通过 /agent 命令手动切换 Agent。


出站标记

ChannelOverrides 里设 prefix_agent_name 给每条出站消息加 agent 名前缀。多个 agent 共享一个频道时有用——读者一眼能看出是哪个 agent 回的。

风格包装效果
Off(默认)不变——跟 agent 文本字节相同
Bracket[agent-name] 回复内容
BoldBracket**[agent-name]** 回复内容(粗体渲染依赖通道的输出格式)
[channels.telegram.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 adapter 默认 OutputFormat::PlainTextsignal-cli 把 Markdown 星号下划线原样渲染,留着会产生可见噪音。要重启 Markdown,在 agent 或频道上覆盖:

[channels.signal.overrides]
output_format = "markdown_v2"

其他通道保留各自的 formatter 默认值——只翻了 Signal。

反应表情与处理状态

几个 adapter 可以贴一个反应到用户消息表示"我在处理",发送回复时移除。各通道独立:

  • Slackreactions_enabled 开关(环境变量 SLACK_REACTIONS,默认 true)。收消息加 👀,回复时换成 ✅。already_reacted / no_reaction 错误静默忽略(fail-open)。
  • Feishu — 收消息加 Typing reaction,回复发送时移除。两个调用都 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 媒体附件

Signal adapter 现在支持发送 ImageVoiceVideoAudioAnimationFileFileDataMediaGroup content block。媒体 URL 被下载并 base64 编码进 /v2/sendbase64_attachmentsMediaGroup 递归调用 send()。不支持的类型(PollSticker …)warn! 优雅降级——纯文本 fallback 或跳过——而不是失败整个回复。

WhatsApp 语音 + DM/群消息策略

WhatsApp adapter 暴露:

  • send_voice(to, audio, mime_type) — Cloud API 模式经 /{phone_number_id}/media 上传,再用返回的 media_idaudio 消息;Gateway 模式 base64 编码后 POST 到 /message/send-voice
  • adapter 上的 dm_policy: DmPolicy(默认 Respond)和 group_policy: GroupPolicy(默认 MentionOnly),形状跟 Telegram 一致。Builder 方法:with_dm_policy()with_group_policy()with_bot_phone()with_bot_name()
  • WhatsAppAdapter::should_handle_message(is_group, text) -> bool 给 bridge / webhook 入口过滤用。

MentionOnly 检查消息文本是否含 bot phone 或 bot name;TriggerOnly 检查 group_trigger_patternsAlwaysNever 短路。

Webhook deliver_only 模式

webhook 通道可作为 pass-through 跑:传入 payload 完全绕过 LLM(无 sanitizer、无 rate limiter、无 agent 查找),直接转发到目标通道。适合只需要让消息落到 Telegram / Slack 等地方的推送通知和带外告警。

[channels.webhook]
enabled = true
deliver_only = true
deliver = "telegram:123456789"     # deliver_only 为 true 时必填

deliver_only = truedeliver 未设时,librefang 启动时打 warn,webhook 保持不激活。扇出在内部用 __deliver_only__ / __deliver_target__ metadata 信号化,adapter trait 签名保持不变。

通道文件下载

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,长跑的守护进程不会堆积孤儿附件。