插件 SDK 开发教程

advanced
## 插件架构概述 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插件 #开发者教程 #龙虾技能库