LobeHub 中的 chat SDK 实践:一次编写 Bot 逻辑,跨 Slack、Teams、Discord 与 GitHub 等平台部署
本文基于 LobeHub 仓库中的 Agent 技能文档 SKILL.md,系统讲解 chat SDK 这套统一 TypeScript 聊天机器人 SDK 的核心概念、事件处理器、流式响应与 JSX 交互卡片,并结合 LobeHub 仓库内真实依赖配置与 5 个自研渠道适配器(微信、QQ、飞书、LINE、iMessage)的实际用法,帮助你掌握"逻辑写一次、多平台部署"的完整技术路径。
一、chat SDK 是什么,以及它在 LobeHub 中的位置
chat SDK 是一个统一的 TypeScript SDK,目标是让开发者只写一份 Bot 逻辑,即可部署到 Slack、Teams、Google Chat、Discord、GitHub、Linear 等多个平台。它的核心抽象是:由一个主入口 Chat 协调各平台适配器(Adapter),并通过可插拔的状态层(State)持久化会话订阅关系。
在 LobeHub 仓库中,这份能力被记录在 Agent 技能目录下的 SKILL.md(frontmatter 中标注 user-invocable: false,即它是供 AI Agent 在构建聊天机器人任务时自动加载的参考技能,而非用户直接调用的命令)。该技能文档给出的标准工作流是:在动手写代码之前,先阅读随包分发的完整文档与类型定义:
node_modules/chat/docs/ # 完整文档(MDX 文件)
node_modules/chat/dist/ # 构建产物类型(.d.ts 文件)
按任务类型选择重点阅读的关键文档:
docs/getting-started.mdx— 安装与初始化指南docs/usage.mdx— 事件处理器、线程(thread)、消息(message)、频道(channel)docs/streaming.mdx— 基于 AI SDK 的流式响应docs/cards.mdx— JSX 交互卡片docs/actions.mdx— 按钮/下拉框处理器docs/modals.mdx— 表单对话框(仅 Slack 支持)docs/adapters/*.mdx— 各平台适配器的接入配置docs/state/*.mdx— 状态适配器配置(Redis、ioredis、memory)
同时建议阅读 node_modules/chat/dist/ 下的 TypeScript 类型,以掌握完整 API 面。
仓库内的真实依赖证据
LobeHub 根 package.json 中固定了 SDK 版本 "chat": "~4.38.0",并且工作区(pnpm-workspace.yaml)纳入了 packages/** 下的多个适配器包。更重要的是,LobeHub 围绕该 SDK 自研了 5 个面向中文生态与个人 IM 渠道的适配器,它们全部依赖 chat ~4.38.0:
| 适配器包 | 渠道 | 包描述(来自各自 package.json) |
|---|---|---|
| chat-adapter-wechat | 微信 | "WeChat (iLink) Bot adapter for chat SDK" |
| chat-adapter-qq | "QQ Bot adapter for chat SDK" | |
| chat-adapter-line | LINE | "LINE Messaging API Bot adapter for chat SDK" |
| chat-adapter-feishu | 飞书/Lark | "Lark/Feishu adapter for chat SDK" |
| chat-adapter-imessage | iMessage | "iMessage adapter for chat SDK via BlueBubbles" |
这说明 LobeHub 不仅消费 chat SDK,还将其适配器机制扩展到了官方文档未覆盖的平台——这也是该技能文档存在的实际背景:为在本仓库中开发此类 Bot 或适配器的 Agent 提供统一的知识基线。
二、快速上手:最小可运行的多平台 Bot
技能文档给出的标准 Quick Start 如下,完整继承了 Chat 构造、Slack 适配器与 Redis 状态三层结构:
import { Chat } from 'chat';
import { createSlackAdapter } from '@chat-adapter/slack';
import { createRedisState } from '@chat-adapter/state-redis';
const bot = new Chat({
userName: 'mybot',
adapters: {
slack: createSlackAdapter({
botToken: process.env.SLACK_BOT_TOKEN!,
signingSecret: process.env.SLACK_SIGNING_SECRET!,
}),
},
state: createRedisState({ url: process.env.REDIS_URL! }),
});
bot.onNewMention(async (thread) => {
await thread.subscribe();
await thread.post("Hello! I'm listening to this thread.");
});
bot.onSubscribedMessage(async (thread, message) => {
await thread.post(`You said: ${message.text}`);
});
逐段拆解关键配置:
userName: 'mybot'— 机器人的名字,事件路由时用于识别"是否 @ 了我";adapters— 平台适配器的映射表,键为平台名(slack),值为createXxxAdapter()的产物。多平台部署时只需在此表里追加条目(如teams、discord),业务代码零改动;state: createRedisState(...)— 生产环境使用 Redis 持久化线程订阅状态;开发环境可换用@chat-adapter/state-memory的内存实现;- 环境变量
SLACK_BOT_TOKEN、SLACK_SIGNING_SECRET、REDIS_URL— 分别对应 Slack Bot Token、Slack 签名密钥、Redis 连接串,属于接入该平台与状态层的必要前提。
这段代码的运行语义是:Bot 在被未订阅的线程中 @ 时自动 subscribe() 加入监听,之后对该线程内每条消息做复读响应——这正是后续所有事件模型的缩影。
三、核心概念:六大抽象的分工
技能文档将 SDK 的 API 面归纳为六个核心概念,理解它们是读懂一切 API 的前提:
- Chat — 主入口,负责协调各适配器并将事件路由到对应处理器;
- Adapters — 平台特定层(Slack、Teams、GChat、Discord、GitHub、Linear),屏蔽各平台 Webhook 协议与消息格式差异;
- State — 可插拔的持久化层,生产用 Redis,开发用 memory;它记住"哪些线程被订阅过",是
onSubscribedMessage能够工作的前提; - Thread — 会话线程对象,提供
post()(发消息)、subscribe()(订阅线程)、startTyping()(输入中状态)等方法; - Message — 归一化消息格式,包含三个字段:
text(纯文本)、formatted(mdast AST 结构化格式)、raw(平台原始数据); - Channel — 线程的容器,支持列出频道与直接向频道发消息。
值得强调的是 Message 的三段式设计:业务逻辑读取 text 即可跨平台通用;需要富文本处理时消费 formatted 的 mdast AST;需要平台特有元信息(如 Slack 附件、Discord embed 原始体)时回退到 raw。这一归一化正是"逻辑写一次"能够成立的技术基础。
四、事件处理器体系
SDK 通过一组 on* 注册方法暴露全部事件。技能文档给出的完整触发条件表如下:
| 处理器 | 触发时机 |
|---|---|
onNewMention |
Bot 在未订阅线程中被 @ |
onSubscribedMessage |
已订阅线程中的任意消息 |
onNewMessage(regex) |
未订阅线程中匹配正则的消息 |
onSlashCommand("/cmd") |
斜杠命令调用 |
onReaction(emojis) |
emoji 反应被添加或移除 |
onAction(actionId) |
按钮点击与下拉框选择 |
onAssistantThreadStarted |
Slack Assistants API 线程被打开 |
onAppHomeOpened |
Slack App Home 标签页被打开 |
从中可以推断出 SDK 的核心事件模型是**"线程订阅制"**:
onNewMention是入口——只有被 @ 才会产生订阅意图,处理器里显式调用thread.subscribe()后,该线程进入已订阅状态(写入 State);- 进入订阅状态后,
onSubscribedMessage接管线程内全部后续消息,无需再 @; onNewMessage(regex)则提供"免 @ 触发"的路径,用于按文本模式(如以特定前缀开头)被动拦截未订阅线程中的消息;onAction(actionId)与 JSX 卡片形成闭环——卡片里的按钮点击会携带actionId回流到 Bot(见第六节)。
其中后两个(onAssistantThreadStarted、onAppHomeOpened)是 Slack 生态专属事件,体现了适配器层在归一化之上仍保留平台特有能力入口的设计。
五、流式响应:AI 回答逐字"打字机"输出
技能文档指出:thread.post() 接受任意 AsyncIterable<string>,因此可以直接对接 AI SDK 的 textStream:
import { ToolLoopAgent } from 'ai';
const agent = new ToolLoopAgent({ model: 'anthropic/claude-4.5-sonnet' });
bot.onNewMention(async (thread, message) => {
const result = await agent.stream({ prompt: message.text });
await thread.post(result.textStream);
});
从签名契约看,SDK 只关心"一个可异步迭代的字符串流",不关心字符串来自哪家模型服务商——LobeHub 作为多模型网关项目,这一设计意味着任何自家 Agent 运行时的输出流都能原样接入聊天渠道。适配器层负责把流式片段映射为各平台的原生增量更新机制(Slack 消息更新、Discord 内容刷新等),对 Bot 逻辑透明。
六、JSX 交互卡片:跨平台的富 UI 描述
SDK 提供了一套类 React 的 JSX 组件来描述交互卡片,先在 tsconfig.json 中配置 jsxImportSource: "chat",即可使用这些组件:Card、CardText、Button、Actions、Fields、Field、Select、SelectOption、Image、Divider、LinkButton、Section、RadioSelect。
文档示例:
await thread.post(
<Card title="Order #1234">
<CardText>Your order has been received!</CardText>
<Actions>
<Button id="approve" style="primary">
Approve
</Button>
<Button id="reject" style="danger">
Reject
</Button>
</Actions>
</Card>,
);
使用要点:
thread.post()的入参是多态的——可以传字符串(普通消息)、AsyncIterable<string>(流式消息),也可以传 JSX 卡片元素;<Button id="approve">的id即 actionId,用户点击后由bot.onAction('approve')处理器接收,形成"发卡 → 点击 → 回调"的完整交互闭环;style支持primary、danger等语义样式,由适配器翻译成各平台按钮的配色/语义;- 表单类对话框(Modals)目前仅 Slack 支持,其余平台会退化为卡片内的
Select/RadioSelect/Fields组合——这也是组件列表里单独提供Fields/Field/RadioSelect的原因。
七、包矩阵:核心 SDK、平台适配器与状态适配器
技能文档列出的完整包清单:
| 包 | 用途 |
|---|---|
chat |
核心 SDK |
@chat-adapter/slack |
Slack |
@chat-adapter/teams |
Microsoft Teams |
@chat-adapter/gchat |
Google Chat |
@chat-adapter/discord |
Discord |
@chat-adapter/github |
GitHub Issues |
@chat-adapter/linear |
Linear Issues |
@chat-adapter/state-redis |
Redis 状态(生产) |
@chat-adapter/state-ioredis |
ioredis 状态(替代方案) |
@chat-adapter/state-memory |
内存状态(开发) |
在 LobeHub 仓库中,该矩阵被进一步扩展为上文表格中的 5 个 @lobechat/chat-adapter-* 包(微信、QQ、LINE、飞书、iMessage)。这些包同样遵循"依赖核心 chat 包 + 实现适配器接口"的模式(各包 package.json 中均声明 "chat": "~4.38.0" 为依赖),并在 pnpm-workspace.yaml 声明的 packages/** 工作区内统一构建——这为"如何为 chat SDK 增加新平台适配器"提供了仓库内可直接参考的范例结构。
八、Webhook 接入:把 SDK 挂到你的 HTTP 框架
每个适配器都通过 bot.webhooks.{platform} 暴露一个 Webhook 处理函数。接入方式是与你的 HTTP 框架路由做绑定,技能文档列出的典型宿主包括 Next.js API Routes、Hono、Express:
// 以 Hono 为例的概念性接法(平台名与处理器一一对应)
app.post('/webhooks/slack', (req) => bot.webhooks.slack(req));
app.post('/webhooks/discord', (req) => bot.webhooks.discord(req));
(以上为基于文档描述的接法示意。)部署侧需要为每个启用的平台适配器配置对应的平台侧回调地址(Slack Events URL、Discord Interactions 等),并保证 signingSecret 等验签配置就位——Slack 适配器的 signingSecret 参数(见第二节 Quick Start)即用于此目的。
九、发布流程:Changesets 驱动的 SDK 多包发版
技能文档还记录了 chat SDK 自身 monorepo 的发布流程:它使用 Changesets 管理版本与 CHANGELOG,任何改变包行为的 PR 都必须附带一个 changeset。操作流程:
pnpm changeset
# → 选择受影响的包(例如 @chat-adapter/slack、chat)
# → 选择 bump 类型:patch(修复)、minor(新功能)、major(破坏性变更)
# → 写一段进入 CHANGELOG 的简短摘要
该命令会在 .changeset/ 目录生成一个描述文件,需随 PR 一起提交。合并入 main 后,Changesets GitHub Action 会自动开出一个 "Version Packages" PR 完成版本号提升与 CHANGELOG 更新,再合并该 PR 即发布到 npm。如果你计划向上游 chat SDK monorepo 贡献功能(例如新增平台适配器支持),需要遵循这套"改动 + changeset"同 PR 提交的约定。
十、小结:技能文档沉淀的三条工程经验
回到 SKILL.md 本身,它对 Agent 与开发者沉淀了三点可复用的工程经验:
- 先读包内文档再写代码——
node_modules/chat/docs/与dist/类型是比任何二手资料都权威的 API 依据; - 用"订阅制线程模型"组织事件——以
onNewMention → subscribe() → onSubscribedMessage为主干,辅以onNewMessage(regex)、onAction扩展触发面; - 适配器与状态都可替换——平台扩展走
adapters表,持久化走state注入,业务逻辑对两者均无感,这也是 LobeHub 能在此机制上自研微信/QQ/飞书/LINE/iMessage 五个渠道适配器的原因。
掌握以上内容后,你可以在 LobeHub 工作区内为任意支持的 IM 渠道编写 Bot、接入流式模型响应,并用 JSX 卡片构建跨平台的富交互界面。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00