跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
TypeScriptNode.jsAI

OpenClaw WebSocket 通道开发:从零构建自定义 AI 通信

综述由AI生成基于 OpenClaw 框架实现自定义 WebSocket 通信通道。通过 Python 搭建中转服务,结合 Node.js 插件 SDK 与 Vue 前端,打通用户浏览器到 AI 模型的实时双向链路。文章详解了插件初始化、状态管理适配器及网关适配器的核心逻辑,重点解析了 defaultRuntime 配置对 UI 状态显示的影响,并提供完整的消息标准化与路由分发流程代码示例。

BackendPro发布于 2026/3/22更新于 2026/5/55 浏览
OpenClaw WebSocket 通道开发:从零构建自定义 AI 通信

OpenClaw WebSocket 通道开发:从零构建自定义 AI 通信

项目概述

本项目旨在基于 OpenClaw 框架,通过插件化方式实现一个自定义的 WebSocket 通信通道。它支持将前端用户消息实时转发至后端 AI 服务,并将 AI 回复回传至前端,形成完整的闭环。

技术栈

  • 前端层:Vue 3 + WebSocket
  • 服务端:Python (aiohttp) + uv
  • 通道层:Node.js (ws) + OpenClaw Plugin SDK
  • AI 层:OpenClaw Gateway + LLM Provider

快速开始

1. 环境准备与启动

首先启动 Python WebSocket 中转服务。进入 websocket-service 目录,使用 uv 安装依赖并运行主程序:

cd websocket-service
uv sync
python app.py

默认监听地址为 ws://localhost:8765。

接着启动 Vue 前端应用:

cd websocket-web
npm install
npm run dev

访问 http://localhost:3000 即可看到聊天界面。该界面具备实时连接状态显示、消息收发日志等功能。

2. 安装与配置通道

在 OpenClaw 中安装 WebSocket 通道插件:

cd websocket-channel
openclaw plugins install .
openclaw plugins list

确认列表中出现 websocket-channel 后,编辑配置文件 ~/.openclaw/config.json(或通过 Web UI):

{
  "channels": {
    "websocket-channel": {
      "enabled": true,
      "config": {
        "enabled": true,
        "wsUrl" 
      
    
  

:
"ws://localhost:8765/openclaw"
}
}
}
}

配置说明:

  • enabled: 启用通道开关。
  • wsUrl: 指向 Python 中转服务的 WebSocket 地址。
  • 默认采用开放模式,无需额外配置 groupPolicy。

重启 OpenClaw Gateway 使配置生效。若使用 macOS 应用,可通过菜单栏操作;命令行则执行 pkill -f openclaw-gateway 后重新运行。

3. 测试验证

打开浏览器访问前端页面,点击'连接'按钮。发送消息如'你好,请介绍一下自己',等待 AI 回复。预期效果应显示 AI 基于 OpenClaw 框架生成的回答。

架构设计

整体流程

数据流向分为入站(前端→AI)和出站(AI→前端)两个方向:

  1. 入站:用户在 Vue 界面输入 → WebSocket 发送至 Python 服务端 → 转发给 Node.js 通道 → 标准化格式 → 调用 OpenClaw API → Gateway 调度 AI 生成回复。
  2. 出站:AI 生成文本 → OpenClaw 回调 deliver → Node.js 通道通过 WebSocket 发回 Python 服务端 → 转发给 Vue 前端 → 界面渲染。

核心组件交互

┌─────────────────┐
│  用户浏览器     │
│  (Vue 前端)     │
└────────┬────────┘
         │ WebSocket
         ▼
┌─────────────────┐
│  Python 服务端   │
│  (aiohttp)      │
└────────┬────────┘
         │ WebSocket
         ▼
┌─────────────────┐
│  Node.js 通道    │
│  (ws 库)        │
└────────┬────────┘
         │ OpenClaw Plugin API
         ▼
┌─────────────────┐
│  OpenClaw       │
│  Gateway        │
└────────┬────────┘
         ▼
┌─────────────────┐
│  AI Provider    │
│  (Qwen/Bailian) │
└─────────────────┘

通道开发详解

1. 初始化与元数据定义

创建插件目录并建立基础文件结构。在 index.ts 中引入必要的类型定义,并声明插件元数据。这里需要明确插件 ID、标签及描述,确保 OpenClaw 能正确识别。

import type { ReplyPayload } from "openclaw/auto-reply/types";
import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
import { createDefaultChannelRuntimeState } from "openclaw/plugin-sdk";

interface WebSocketChannelConnection {
  ws: any;
  accountId: string;
}

interface WebSocketChannelAccount {
  accountId: string;
  wsUrl: string;
  enabled?: boolean;
  configured?: boolean;
  dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
}

const connections = new Map<string, WebSocketChannelConnection>();
let pluginRuntime: any = null;

const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
  id: "websocket-channel",
  meta: {
    id: "websocket-channel",
    label: "Websocket Channel",
    selectionLabel: "Websocket Channel (Custom)",
    docsPath: "/channels/websocket-channel",
    blurb: "WebSocket based messaging channel.",
    aliases: ["ws"],
  },
};

2. 配置适配器

配置适配器负责读取全局配置并解析账户信息。我们需要列出账户 ID,并从中提取 WebSocket URL 等关键参数。

config: {
  /**
   * 列出所有配置的账户 ID
   */
  listAccountIds: (cfg: OpenClawConfig) => {
    return ["default"];
  },
  /**
   * 解析账户配置
   */
  resolveAccount: (cfg: OpenClawConfig, accountId: string) => {
    const channelCfg = cfg.channels?.["websocket-channel"];
    if (!channelCfg || !channelCfg.config) {
      return undefined;
    }
    const config = channelCfg.config as any;
    return {
      accountId: "default",
      wsUrl: config.wsUrl || "ws://localhost:8765/openclaw",
      enabled: config.enabled !== false,
    };
  },
  /**
   * 检查账户是否已配置
   */
  isConfigured: async (account, cfg) => {
    return Boolean(account.wsUrl && account.wsUrl.trim() !== "");
  },
}

3. 状态管理适配器

这是容易被忽略但至关重要的部分。OpenClaw 的 UI 依赖 defaultRuntime 来知道需要展示哪些状态字段。如果未定义,即使代码中设置了 connected: true,UI 仍可能显示异常。

status: {
  /**
   * 默认运行时状态模板
   * ⚠️ 必须实现,否则 UI 会显示 "0/1 connected"
   */
  defaultRuntime: createDefaultChannelRuntimeState("default", {
    wsUrl: null,
    connected: false,
    groupPolicy: null,
  }),
  /**
   * 构建通道摘要(用于 UI 显示)
   */
  buildChannelSummary: ({ snapshot }) => ({
    wsUrl: snapshot.wsUrl ?? null,
    connected: snapshot.connected ?? null,
    groupPolicy: snapshot.groupPolicy ?? null,
  }),
  /**
   * 构建账户完整快照
   */
  buildAccountSnapshot: ({ account, runtime }) => ({
    accountId: account.accountId,
    enabled: account.enabled,
    configured: account.configured,
    wsUrl: account.wsUrl,
    running: runtime?.running ?? false,
    connected: runtime?.connected ?? false,
    groupPolicy: runtime?.groupPolicy ?? null,
    lastStartAt: runtime?.lastStartAt ?? null,
    lastStopAt: runtime?.lastStopAt ?? null,
    lastError: runtime?.lastError ?? null,
  }),
}

关键点:在 startAccount 开始时调用 ctx.setStatus({ connected: true }),配合上述 defaultRuntime 定义,UI 就能正确显示连接状态。

4. 网关适配器(核心逻辑)

这是处理消息流转的核心。我们需要建立 WebSocket 连接,监听消息事件,并将其标准化为 OpenClaw 可识别的格式。

gateway: {
  /**
   * 启动 WebSocket 账户连接
   */
  startAccount: async (ctx) => {
    const { log, account, abortSignal, cfg } = ctx;
    log?.info(`[websocket-channel] Starting WebSocket Channel for ${account.accountId}`);

    // 获取 runtime API
    const runtime = pluginRuntime;

    // ⭐ 关键:设置初始状态为已连接
    ctx.setStatus({
      accountId: account.accountId,
      wsUrl: account.wsUrl,
      running: true,
      connected: true,
    });
    log?.info(`[websocket-channel] Status set: connected=true, running=true`);

    // 创建 WebSocket 连接
    const WebSocketLib = await import("ws");
    const ws = new (WebSocketLib.default as any)(account.wsUrl);

    // 存储连接
    connections.set(account.accountId, { ws, accountId: account.accountId });

    // 监听消息事件
    ws.on("message", async (data: Buffer) => {
      try {
        // 1. 解析原始消息
        const rawData = data.toString();
        const eventData = JSON.parse(rawData);
        const innerData = eventData.data || {};

        // 2. 标准化消息
        const normalizedMessage = {
          id: `${eventData.source || "websocket"}-${Date.now()}`,
          channel: "websocket-channel",
          accountId: account.accountId,
          senderId: innerData.source || eventData.source || "unknown",
          senderName: innerData.source || eventData.source || "Unknown",
          text: innerData.content || innerData.text || "",
          timestamp: innerData.timestamp || Date.now().toISOString(),
          isGroup: false,
          groupId: undefined,
          attachments: [],
          metadata: {},
        };

        log?.info(`[websocket-channel] 📨 Received: "${normalizedMessage.text}" from ${normalizedMessage.senderId}`);

        // 3. 解析路由
        const route = runtime.channel.routing.resolveAgentRoute({
          cfg,
          channel: "websocket-channel",
          accountId: account.accountId,
          peer: {
            kind: "direct",
            id: normalizedMessage.senderId,
          },
        });

        // 4. 构建消息上下文
        const ctxPayload = runtime.channel.reply.finalizeInboundContext({
          Body: normalizedMessage.text,
          BodyForAgent: normalizedMessage.text,
          From: normalizedMessage.senderId,
          To: undefined,
          SessionKey: route.sessionKey,
          AccountId: route.accountId,
          ChatType: "direct",
          SenderName: normalizedMessage.senderName,
          SenderId: normalizedMessage.senderId,
          Provider: "websocket-channel",
          Surface: "websocket-channel",
          MessageSid: normalizedMessage.id,
          Timestamp: Date.now(),
        });

        // 5. 调用框架调度器
        await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
          ctx: ctxPayload,
          cfg: cfg,
          dispatcherOptions: {
            deliver: async (payload: ReplyPayload, { kind }) => {
              log?.info(`[websocket-channel] Delivering ${kind} reply via WebSocket...`);
              const currentConn = connections.get(account.accountId);
              if (!currentConn || !currentConn.ws || currentConn.ws.readyState !== 1) {
                throw new Error("No WebSocket connection available");
              }
              // 发送 AI 回复
              currentConn.ws.send(JSON.stringify({
                type: "reply",
                content: payload.text || "",
                kind,
              }));
            },
            onError: (err, { kind }) => {
              log?.error(`[websocket-channel] Delivery error for ${kind}: ${err.message}`);
            },
          },
        });
        log?.info(`[websocket-channel] Message dispatched successfully`);
      } catch (err) {
        log?.error(`[websocket-channel] Failed to process message: ${err.message}`);
      }
    });

    // 监听错误和关闭
    ws.on("error", (err: Error) => {
      log?.error(`[websocket-channel] ❌ WebSocket error: ${err.message}`);
      connections.delete(account.accountId);
    });

    ws.on("close", () => {
      log?.info(`[websocket-channel] 🔴 Connection closed`);
      connections.delete(account.accountId);
    });

    // 监听中止信号
    abortSignal.addEventListener("abort", () => {
      log?.info(`[websocket-channel] ⏹️ Abort requested`);
      ws.close();
    });

    // 保持连接运行
    await Promise.race([
      new Promise<void>((resolve) => {
        abortSignal.addEventListener("abort", () => resolve());
      }),
    ]);

    connections.delete(account.accountId);
  },
}

5. 注册入口

最后,在 index.ts 导出注册函数,将插件挂载到 OpenClaw 运行时。

/**
 * 注册插件入口
 * @param api - 插件 API
 */
export default function register(api: any) {
  console.log("[websocket-channel] Registering WebSocket Channel plugin");
  pluginRuntime = api.runtime;
  api.registerChannel({ plugin: WebSocketChannel });
}

参考资源

  • OpenClaw 官方文档
  • Plugin SDK 源码
  • 通道开发指南
  • Channel 插件开发指导手册
  • 示例项目仓库

目录

  1. OpenClaw WebSocket 通道开发:从零构建自定义 AI 通信
  2. 项目概述
  3. 技术栈
  4. 快速开始
  5. 1. 环境准备与启动
  6. 2. 安装与配置通道
  7. 3. 测试验证
  8. 架构设计
  9. 整体流程
  10. 核心组件交互
  11. 通道开发详解
  12. 1. 初始化与元数据定义
  13. 2. 配置适配器
  14. 3. 状态管理适配器
  15. 4. 网关适配器(核心逻辑)
  16. 5. 注册入口
  17. 参考资源
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 从 Copilot 到 Agentic:快手重构人 AI 流程研发铁三角实践
  • 直流无刷电机 FOC 控制算法原理与 STM32 实现
  • 我用Openclaw + Claude搭了一套自动写作系统,每天省3小时
  • C++ 最大公约数(GCD)算法的三种实现方式
  • MySQL MVCC 原理详解:从零理解并发控制
  • 人形机器人 VR 遥操作技术实现指南
  • 知网 AIGC 检测原理及降低 AI 疑似度策略
  • 智能仿真无人机平台技术笔记:多线程任务分配与碰撞规避
  • Java 基础进阶:数据类型与面向对象
  • C++ STL 容器详解:map 与 set 原理及实战
  • 零基础学习 Python 必备开发工具与库指南
  • 前端 PWA 技术详解:离线缓存与推送通知实现
  • 机器人测试方法与工具解析
  • FPGA 开发常用软件对比:Vivado、Quartus 与 ModelSim
  • Flutter 三方库 ethereum_addresses 的鸿蒙化适配指南
  • Ubuntu 20.04 安装 Ollama 及 Open WebUI 部署大模型教程
  • 主流音视频传输协议(DP、HDMI、USB4 等)性能对比
  • Spring Boot 数据访问与数据库集成详解
  • 节点小宝 4.0 macOS 客户端发布:原生远程文件与跨设备同步
  • Fabric:开源 AI 集成框架与核心功能解析

相关免费在线工具

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online