Skip to content

53 函数级剖析-通道适配器框架实现

目标

这篇只做一件事:把“通道适配器框架”拆成可照抄的工程实现。


一、主干调用链(先背下来)

  1. 插件注册:extensions/<your-channel>/index.ts -> api.registerChannel({ plugin })
  2. 注册入库:src/plugins/registry.ts -> registerChannel(...) -> registry.channels.push(...)
  3. 网关启动:src/gateway/server.impl.ts -> createChannelManager(...)
  4. 通道启动:src/gateway/server-channels.ts -> startChannels() / startChannel(...)
  5. 入站路由:src/routing/resolve-route.ts -> resolveAgentRoute(...)
  6. 出站发送:src/infra/outbound/deliver.ts -> deliverOutboundPayloads(...)
  7. 状态体检: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.tsregisterChannel(...) 做三件事:

  1. 归一化参数(支持 {plugin} 或直接 plugin)
  2. 校验 plugin.id
  3. push 到 registry.channels

从这一步开始,listChannelPlugins() 就能拿到你的通道。


四、账号生命周期是怎么跑起来的

文件:src/gateway/server-channels.ts

createChannelManager(...) 是总控,关键函数:

  1. startChannel(channelId, accountId?)
  2. stopChannel(channelId, accountId?)
  3. startChannels()
  4. markChannelLoggedOut(...)
  5. getRuntimeSnapshot()

关键原理

  1. plugin.config.listAccountIds(cfg) 获取账号列表
  2. 每个账号调用 plugin.gateway.startAccount(...)
  3. 运行态写入 runtime store(running/lastError/lastStartAt)
  4. 停止时触发账号级 abort + plugin.gateway.stopAccount(...)(若实现)

所以它天然支持“一个通道多个账号并行运行”。


五、入站和出站如何解耦

入站(消息进来)

通道监控器拿到消息后,调用:

ts
resolveAgentRoute({ channel, accountId, peer, cfg })

返回 agentId/sessionKey,再进入 agent 执行流水线。

出站(回复出去)

deliverOutboundPayloads(...) 内部拿到 plugin.outbound,再调用:

  1. sendText(...)
  2. 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 }),
  },
}

七、落地顺序(防止走弯路)

  1. 先只做单账号 + sendText
  2. 再做 gateway.startAccount 入站监听
  3. 再补 status.probeAccount
  4. 再补多账号与 auth
  5. 最后做媒体、线程、反应、命令等高级能力

八、常见失败点(真实工程高频)

  1. 只实现 outbound,没实现 gateway:能发不能收。
  2. config.listAccountIds 写错:网关启动不到账号。
  3. 忘记 registerChannel:插件加载了但通道不可见。
  4. 没有账号级状态:排障时无法区分哪个账号挂了。
  5. 把平台 SDK 逻辑写进核心层:后续扩平台成本爆炸。

九、这篇对应你提的问题

你问“通道适配器框架没讲重点”。
重点就是三句话:

  1. ChannelPlugin 合同统一所有平台。
  2. ChannelManager 管账号生命周期。
  3. resolveAgentRoute + deliverOutboundPayloads 连接入站路由和出站发送。

掌握这三点,你就能从 0 到 1 实现同类系统的通道层。

用工程视角拆解 AI 智能体框架