插件架构概述
OpenClaw 的插件系统允许你扩展 Gateway 的核心功能。插件运行在 Gateway 进程中,可以: - 添加新的聊天渠道(如自定义的即时通讯平台) - 添加新的模型提供商(如私有部署的 LLM) - 扩展 Gateway 的内置功能 ``
┌─────────────────────────────────┐
│ Gateway 进程 │
├─────────────────────────────────┤
│ 核心模块 │
│ ├── 会话管理 │
│ ├── 上下文引擎 │
│ └── 工具系统 │
├─────────────────────────────────┤
│ 插件层 │
│ ├── 渠道插件(Channel Plugins) │ ← 添加新聊天渠道
│ ├── 提供商插件(Provider) │ ← 添加新模型提供商
│ └── 自定义插件 │ ← 扩展功能
└─────────────────────────────────┘
`
插件类型
| 类型 | 说明 | 示例 |
|------|------|------|
| 渠道插件 | 连接新的聊天平台 | 微信企业版、钉钉、自定义 WebSocket |
| 提供商插件 | 接入新的 AI 模型 | 私有 LLM、自定义 API |
| 功能插件 | 扩展 Gateway 功能 | Voice Call、自定义工具 |
SDK 环境搭建
前置要求
- Node.js 22.14+ 或 24+
- pnpm(推荐)或 npm
- TypeScript 5.0+
- OpenClaw 已安装
初始化插件项目
`bash
创建项目目录
mkdir my-openclaw-plugin
cd my-openclaw-plugin
初始化 pnpm 项目
pnpm init
安装 SDK 依赖
pnpm add -D @openclaw/plugin-sdk typescript @types/node
初始化 TypeScript
npx tsc --init
`
推荐的 tsconfig.json
`json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"]
}
`
项目结构
`
my-openclaw-plugin/
├── src/
│ ├── index.ts # 插件入口点
│ └── ... # 其他源文件
├── openclaw.plugin.json # 插件 manifest(必须)
├── package.json
├── tsconfig.json
└── README.md
`
Manifest 格式
每个插件必须有一个 openclaw.plugin.json 文件,描述插件的元数据和配置。
基本结构
`json
{
"name": "my-awesome-plugin",
"version": "1.0.0",
"displayName": "My Awesome Plugin",
"description": "一个示例 OpenClaw 插件",
"author": "your-name",
"license": "MIT",
"main": "dist/index.js",
"type": "channel",
"openclaw": {
"minVersion": "1.0.0"
},
"config": {
"apiKey": {
"type": "string",
"description": "API 密钥",
"required": true,
"secret": true
},
"webhookPort": {
"type": "number",
"description": "Webhook 监听端口",
"default": 8080
}
}
}
`
字段说明
| 字段 | 必填 | 说明 |
|------|------|------|
| name | 是 | 插件唯一标识(kebab-case) |
| version | 是 | 语义化版本号 |
| displayName | 是 | 显示名称 |
| description | 是 | 插件描述 |
| main | 是 | 入口文件路径 |
| type | 是 | 插件类型:channel / provider / extension |
| openclaw.minVersion | 否 | 最低 OpenClaw 版本要求 |
| config | 否 | 插件配置项定义 |
Config 配置项类型
`json
{
"config": {
"stringField": {
"type": "string",
"description": "字符串配置",
"required": true,
"default": "hello"
},
"numberField": {
"type": "number",
"description": "数字配置",
"default": 3000
},
"boolField": {
"type": "boolean",
"description": "布尔配置",
"default": true
},
"secretField": {
"type": "string",
"description": "密钥(不会明文显示)",
"secret": true
}
}
}
`
渠道插件开发
渠道插件让 OpenClaw 连接到新的聊天平台。
入口点结构
`typescript
import { ChannelPlugin, Message, SendOptions } from '@openclaw/plugin-sdk'
export default class MyChannelPlugin extends ChannelPlugin {
// 插件初始化
async onLoad(): Promise {
const apiKey = this.config.get('apiKey')
// 初始化连接、启动 webhook 等
this.logger.info('插件已加载')
}
// 发送消息到渠道
async sendMessage(message: Message, options: SendOptions): Promise {
const { text, chatId } = message
// 调用渠道 API 发送消息
await this.api.send(chatId, text)
}
// 发送输入指示器
async sendTypingIndicator(chatId: string): Promise {
await this.api.sendTyping(chatId)
}
// 插件卸载
async onUnload(): Promise {
// 清理资源、关闭连接
this.logger.info('插件已卸载')
}
}
`
接收消息
渠道插件通过回调将收到的消息传递给 Gateway:
`typescript
export default class MyChannelPlugin extends ChannelPlugin {
async onLoad(): Promise {
// 监听来自渠道的消息
this.webhookServer.on('message', (event) => {
// 将消息传递给 Gateway 处理
this.emitMessage({
text: event.text,
chatId: event.chatId,
senderId: event.userId,
senderName: event.userName,
timestamp: Date.now()
})
})
}
}
`
处理媒体消息
`typescript
async sendMessage(message: Message, options: SendOptions): Promise {
if (message.attachments?.length) {
for (const attachment of message.attachments) {
if (attachment.type === 'image') {
await this.api.sendImage(message.chatId, attachment.url)
} else if (attachment.type === 'file') {
await this.api.sendFile(message.chatId, attachment.url, attachment.name)
}
}
}
if (message.text) {
await this.api.sendText(message.chatId, message.text)
}
}
`
提供商插件开发
提供商插件让 OpenClaw 接入新的 AI 模型。
入口点结构
`typescript
import { ProviderPlugin, ChatRequest, ChatResponse } from '@openclaw/plugin-sdk'
export default class MyProviderPlugin extends ProviderPlugin {
async onLoad(): Promise {
const apiKey = this.config.get('apiKey')
const baseUrl = this.config.get('baseUrl')
this.logger.info('提供商插件已加载')
}
// 发送聊天请求
async chat(request: ChatRequest): Promise {
const { messages, model, temperature, maxTokens } = request
const response = await fetch( ${this.baseUrl}/v1/chat/completions, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${this.apiKey}
},
body: JSON.stringify({
model,
messages,
temperature,
max_tokens: maxTokens
})
})
const data = await response.json()
return {
content: data.choices[0].message.content,
usage: {
promptTokens: data.usage.prompt_tokens,
completionTokens: data.usage.completion_tokens
}
}
}
// 流式聊天(可选)
async *chatStream(request: ChatRequest): AsyncGenerator {
// 实现 SSE 流式响应
const response = await fetch( ${this.baseUrl}/v1/chat/completions, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${this.apiKey}
},
body: JSON.stringify({
...request,
stream: true
})
})
// 解析 SSE 流
for await (const chunk of this.parseSSE(response.body)) {
yield chunk.content
}
}
// 列出可用模型
async listModels(): Promise {
return ['my-model-7b', 'my-model-13b', 'my-model-70b']
}
}
`
入口点(Entrypoints)
插件 SDK 提供了多个生命周期入口点:
| 入口点 | 说明 | 调用时机 |
|--------|------|---------|
| onLoad() | 插件加载 | Gateway 启动或插件安装时 |
| onUnload() | 插件卸载 | Gateway 停止或插件移除时 |
| onConfigChange() | 配置变更 | 用户修改插件配置时 |
| onHealthCheck() | 健康检查 | Gateway 执行健康检查时 |
`typescript
export default class MyPlugin extends ChannelPlugin {
async onLoad(): Promise {
// 初始化资源
}
async onUnload(): Promise {
// 清理资源
}
async onConfigChange(key: string, value: any): Promise {
// 响应配置变更
if (key === 'apiKey') {
await this.reconnect()
}
}
async onHealthCheck(): Promise {
// 返回 true 表示健康
return this.isConnected
}
}
`
运行时 API
插件在运行时可以访问以下 API:
Logger
`typescript
this.logger.info('信息日志')
this.logger.warn('警告日志')
this.logger.error('错误日志', error)
this.logger.debug('调试日志')
`
Config
`typescript
// 读取配置
const apiKey = this.config.get('apiKey')
const port = this.config.get('webhookPort', 8080) // 带默认值
// 检查配置是否存在
if (this.config.has('optionalField')) {
// ...
}
`
Storage
插件可以使用持久化存储:
`typescript
// 存储数据
await this.storage.set('lastSync', Date.now())
// 读取数据
const lastSync = await this.storage.get('lastSync')
// 删除数据
await this.storage.delete('lastSync')
`
Events
插件间通信:
`typescript
// 发送事件
this.emit('my-event', { data: 'hello' })
// 监听事件
this.on('other-plugin-event', (data) => {
this.logger.info('收到事件', data)
})
`
测试插件
单元测试
使用 Vitest 测试插件逻辑:
`typescript
import { describe, it, expect } from 'vitest'
import MyPlugin from '../src/index'
describe('MyPlugin', () => {
it('should parse message correctly', () => {
const plugin = new MyPlugin()
const result = plugin.parseMessage({
text: 'hello',
chatId: '123'
})
expect(result.text).toBe('hello')
})
})
`
本地测试
在本地 Gateway 中加载开发中的插件:
`json
// openclaw.json
{
"plugins": {
"entries": [
{
"source": "local",
"path": "/path/to/my-openclaw-plugin"
}
]
}
}
`
重启 Gateway 后插件会自动加载:
`bash
openclaw gateway restart
`
调试技巧
`bash
查看插件加载日志
openclaw logs | grep "plugin"
启用调试模式
OPENCLAW_LOG_LEVEL=debug openclaw gateway run
`
发布插件
准备发布
1. 确保 openclaw.plugin.json 信息完整
2. 编写 README.md 文档
3. 构建 TypeScript:
`bash
pnpm build
`
4. 检查 package.json 的 files 字段:
`json
{
"files": [
"dist/",
"openclaw.plugin.json",
"README.md"
]
}
`
发布到 ClawHub
`bash
登录 ClawHub
clawhub login
发布插件
clawhub publish
`
发布后,其他用户可以通过以下命令安装:
`bash
openclaw plugins install your-plugin-name
`
版本更新
更新 openclaw.plugin.json 和 package.json 中的版本号,然后重新发布:
`bash
更新版本
npm version patch # 或 minor / major
重新发布
clawhub publish
`
开发最佳实践
1. 错误处理:所有异步操作都要 try-catch,避免未捕获异常导致 Gateway 崩溃
2. 资源清理:在 onUnload() 中关闭所有连接和定时器
3. 配置验证:在 onLoad() 中验证必要配置是否存在
4. 日志规范:使用 this.logger 而非 console.log
5. 类型安全:充分利用 TypeScript 类型系统
6. 文档完善:README 中说明配置项、使用方法和注意事项
小结
- 插件运行在 Gateway 进程中,可以扩展渠道、提供商和功能
- openclaw.plugin.json` 是插件的核心配置文件
- SDK 提供了完整的生命周期管理和运行时 API
- 本地开发时可以直接加载插件目录进行测试
- 通过 ClawHub 发布插件供其他用户使用
#插件SDK #插件开发 #OpenClaw插件 #开发者教程 #龙虾技能库