跳到主要内容TypeScriptNode.jsAI
OpenClaw WebSocket 通道开发:从零构建自定义 AI 通信
综述由AI生成基于 OpenClaw 框架实现自定义 WebSocket 通信通道。通过 Python 搭建中转服务,结合 Node.js 插件 SDK 与 Vue 前端,打通用户浏览器到 AI 模型的实时双向链路。文章详解了插件初始化、状态管理适配器及网关适配器的核心逻辑,重点解析了 defaultRuntime 配置对 UI 状态显示的影响,并提供完整的消息标准化与路由分发流程代码示例。
BackendPro5 浏览 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→前端)两个方向:
- 入站:用户在 Vue 界面输入 → WebSocket 发送至 Python 服务端 → 转发给 Node.js 通道 → 标准化格式 → 调用 OpenClaw API → Gateway 调度 AI 生成回复。
- 出站: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: {
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: {
defaultRuntime: createDefaultChannelRuntimeState("default", {
wsUrl: null,
connected: false,
groupPolicy: null,
}),
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: {
startAccount: async (ctx) => {
const { log, account, abortSignal, cfg } = ctx;
log?.info(`[websocket-channel] Starting WebSocket Channel for ${account.accountId}`);
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`);
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 {
const rawData = data.toString();
const eventData = JSON.parse(rawData);
const innerData = eventData.data || {};
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}`);
const route = runtime.channel.routing.resolveAgentRoute({
cfg,
channel: "websocket-channel",
accountId: account.accountId,
peer: {
kind: "direct",
id: normalizedMessage.senderId,
},
});
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(),
});
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");
}
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 运行时。
export default function register(api: any) {
console.log("[websocket-channel] Registering WebSocket Channel plugin");
pluginRuntime = api.runtime;
api.registerChannel({ plugin: WebSocketChannel });
}
参考资源
相关免费在线工具
- 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