53 函数级剖析-通道适配器框架实现
目标
这篇只做一件事:把“通道适配器框架”拆成可照抄的工程实现。
一、主干调用链(先背下来)
- 插件注册:
extensions/<your-channel>/index.ts->api.registerChannel({ plugin }) - 注册入库:
src/plugins/registry.ts->registerChannel(...)->registry.channels.push(...) - 网关启动:
src/gateway/server.impl.ts->createChannelManager(...) - 通道启动:
src/gateway/server-channels.ts->startChannels()/startChannel(...) - 入站路由:
src/routing/resolve-route.ts->resolveAgentRoute(...) - 出站发送:
src/infra/outbound/deliver.ts->deliverOutboundPayloads(...) - 状态体检:
src/gateway/server-methods/channels.ts->"channels.status"
二、你必须实现的适配器接口
文件:src/channels/plugins/types.plugin.ts
最小必填:
ts
type ChannelPlugin = {
id: ChannelId
meta: ChannelMeta
capabilities: ChannelCapabilities
config: ChannelConfigAdapter
}生产可用建议至少再加:
ts
{
gateway: { startAccount(...) {} },
outbound: { sendText(...) {}, sendMedia(...) {} },
status: { probeAccount(...) {} },
auth: { login(...) {}, logoutAccount(...) {} }
}三、注册是怎么接进核心系统的
1) 插件侧
ts
export default {
id: "mychat",
register(api) {
api.registerChannel({ plugin: mychatPlugin })
},
}2) 核心侧
src/plugins/registry.ts 中 registerChannel(...) 做三件事:
- 归一化参数(支持
{plugin}或直接 plugin) - 校验
plugin.id - push 到
registry.channels
从这一步开始,listChannelPlugins() 就能拿到你的通道。
四、账号生命周期是怎么跑起来的
文件:src/gateway/server-channels.ts
createChannelManager(...) 是总控,关键函数:
startChannel(channelId, accountId?)stopChannel(channelId, accountId?)startChannels()markChannelLoggedOut(...)getRuntimeSnapshot()
关键原理
- 按
plugin.config.listAccountIds(cfg)获取账号列表 - 每个账号调用
plugin.gateway.startAccount(...) - 运行态写入 runtime store(running/lastError/lastStartAt)
- 停止时触发账号级 abort +
plugin.gateway.stopAccount(...)(若实现)
所以它天然支持“一个通道多个账号并行运行”。
五、入站和出站如何解耦
入站(消息进来)
通道监控器拿到消息后,调用:
ts
resolveAgentRoute({ channel, accountId, peer, cfg })返回 agentId/sessionKey,再进入 agent 执行流水线。
出站(回复出去)
deliverOutboundPayloads(...) 内部拿到 plugin.outbound,再调用:
sendText(...)sendMedia(...)
统一做文本分片、媒体分发、结果收集。
结论:AI 核心不关心 Telegram/Discord API 细节,平台细节都在适配器里。
六、最小可运行模板(你可以直接照着做)
ts
import type { ChannelPlugin } from "openclaw/plugin-sdk"
export const mychatPlugin: ChannelPlugin = {
id: "mychat",
meta: {
id: "mychat",
label: "MyChat",
selectionLabel: "MyChat",
docsPath: "/channels/mychat",
blurb: "MyChat channel plugin",
},
capabilities: { chatTypes: ["direct"], media: false },
config: {
listAccountIds: (cfg) => Object.keys(cfg.channels?.mychat?.accounts ?? { default: {} }),
resolveAccount: (cfg, accountId) =>
cfg.channels?.mychat?.accounts?.[accountId ?? "default"] ?? { accountId: "default", enabled: true },
},
gateway: {
startAccount: async (ctx) => {
// 这里启动 webhook 或长连接监听
ctx.setStatus({ accountId: ctx.accountId, running: true })
return async () => {
// 这里实现停止逻辑
}
},
},
outbound: {
deliveryMode: "direct",
sendText: async ({ target, text }) => {
// 调你自己的平台 SDK 发消息
return { ok: true, target, text }
},
sendMedia: async ({ target, mediaUrl, caption }) => {
return { ok: true, target, mediaUrl, caption }
},
},
status: {
probeAccount: async () => ({ ok: true }),
},
}七、落地顺序(防止走弯路)
- 先只做单账号 +
sendText - 再做
gateway.startAccount入站监听 - 再补
status.probeAccount - 再补多账号与
auth - 最后做媒体、线程、反应、命令等高级能力
八、常见失败点(真实工程高频)
- 只实现 outbound,没实现 gateway:能发不能收。
config.listAccountIds写错:网关启动不到账号。- 忘记
registerChannel:插件加载了但通道不可见。 - 没有账号级状态:排障时无法区分哪个账号挂了。
- 把平台 SDK 逻辑写进核心层:后续扩平台成本爆炸。
九、这篇对应你提的问题
你问“通道适配器框架没讲重点”。
重点就是三句话:
- 用
ChannelPlugin合同统一所有平台。 - 用
ChannelManager管账号生命周期。 - 用
resolveAgentRoute + deliverOutboundPayloads连接入站路由和出站发送。
掌握这三点,你就能从 0 到 1 实现同类系统的通道层。