致Java后端的OpenClaw插件开发指南

欢迎来到 OpenClaw 的高阶扩展世界。作为一名 Java 后端工程师,你在阅读这份指南时会体验到强烈的”既视感”。
请在脑海中建立这样一个核心映射:
你可以将 OpenClaw 核心网关 (Gateway) 视作一个庞大的 Spring Boot 核心容器 (IoC Container)。而你即将开发的每一个插件 (Plugin),本质上就是一个个遵循高度规范的 Spring Boot Starter。
核心网关负责基础的事件分发、状态机流转、持久化(类似 Spring 的 DispatcherServlet 和 JPA 层),而你的插件通过 SPI (Service Provider Interface) 契约、AOP 切面拦截以及依赖注入,向网关的核心注册表中注入新的能力(新的模型、新的聊天渠道、新的业务工具)。
本指南将带你穿透官方文档的迷雾,掌握插件开发的底层脉络、关键对象定义、核心生命周期以及企业级最佳实践。
🏛️ 第一章:全局视界 —— 插件系统的底层哲学
在动手写任何代码之前,我们必须理解 OpenClaw 的能力所有权模型(Capability Ownership Model)。OpenClaw 坚决反对”一个插件是个大杂烩”的设计,它要求插件职责高度内聚。

1. 插件的四大形态 (Plugin Shapes)
网关通过扫描你的代码注册行为,将你的插件归类为以下四种形态之一:
Plain-capability(单一能力型):类似于微服务中的单一数据源服务。只注册一种核心能力,例如仅提供一个第三方的图片生成大模型接口(Image Generation Provider)。
Hybrid-capability(混合能力型):企业级主推模式。这是按厂商(Vendor)边界划分的巨无霸 Starter。例如 openai 插件,它在一个包内同时注册了文本推理、TTS(语音)、图像生成、多模态理解等所有 OpenAI 相关的能力。
Non-capability(非能力型):不提供底层大模型或通讯渠道,而是提供工具 (Tools) 或 后台服务 (Services)。例如一个查询公司内部数据库的插件。
Hook-only(纯钩子型):完全等价于 Java 中的全局 AOP (@Aspect) 切面拦截器。它不注册具体业务,只监听诸如 before_tool_call(工具调用前)等事件,用于做全局风控、敏感词拦截或统一审批流。
2. 插件的生命周期与加载管线 (Load Pipeline)
不要一上来就写业务逻辑,你需要知道网关是如何拉起你的”Starter”的。OpenClaw 的加载严格分为控制平面与数据平面:
发现 (Discovery):扫描目录,寻找 openclaw.plugin.json。
启用与验证 (Validation):读取 JSON 进行 Schema 校验。⚠️ 注意:此阶段绝对不执行任何 JS/TS 业务代码。
加载运行时 (Runtime Loading):通过 JITI (Just-In-Time Import) 加载你的原生 TS/JS 模块,并调用你的入口方法 register(api)。
消费 (Consumption):核心网关从中央注册表中读取你注册的 Bean(能力),暴露给终端的 Agent 或聊天界面。
📜 第二章:基石与契约 —— 目录结构与核心配置剖析
开发插件的起手式是搭建标准的项目目录,并写好两个配置文件。它们构成了 OpenClaw 的 控制平面 (Control Plane) 和 数据平面 (Data Plane)。

2.1 插件项目的标准目录结构
一个符合官方规范且结构清晰的插件项目,通常具有如下形态:
my-awesome-plugin/├── package.json # 数据平面:Node.js 依赖管理与扩展入口声明├── tsconfig.json # TypeScript 编译配置├── openclaw.plugin.json # 控制平面:核心的静态元数据注册表├── src/│ ├── index.ts # 【核心】运行时主入口 (Full 模式)│ ├── setup-entry.ts # 【核心】轻量级引导程序 (Setup 模式,可选)│ └── tools/ # 具体的工具业务逻辑拆分│ └── myTool.ts└── tests/ └── index.test.ts # Vitest 单元与契约测试
2.2 openclaw.plugin.json (控制平面 / 静态元数据注册表)
这是重中之重。网关必须在不加载你任何代码的情况下,快速地读取它。这类似于 Java 的 META-INF/spring.factories。这里给出一个完整的配置文件实例:
{ "id": "my-corp-database-tool", "name": "Corp Database Helper", "version": "1.0.0", "description": "提供公司内部 MySQL 数据库的查询与操作工具", // 【必填】声明插件配置的 JSON Schema,网关据此校验用户配置 "configSchema": { "type": "object", "properties": { "dbHost": { "type": "string" }, "dbPort": { "type": "number", "default": 3306 } }, "additionalProperties":false }, // 【核心】能力契约声明。网关在启动时进行"契约防漂移测试" // 必须与你在 register(api) 中实际注册的代码一致! "contracts": { "tools": ["query_mysql_db"], "speechProviders": ["my-tts-engine"] }, // 激活计划元数据:告诉网关何时触发"懒加载" "activation": { "onCommands": ["/db_query"] }, // 用于在不加载庞大运行时的前提下,渲染控制台 UI 的元数据 "setup": { "providers": [ { "id": "my-corp-llm", "envVars": ["CORP_DB_PASSWORD"] } ] }}
2.3 package.json (数据平面 / 依赖与运行时入口)
除了常规的 npm 依赖,必须增加一个 openclaw 对象块来指定代码入口:
{ "name": "my-corp-database-tool", "version": "1.0.0", "dependencies": { "mysql2": "^3.0.0" }, "openclaw": { "extensions": ["dist/index.js"], // 声明真正的业务代码入口 (编译后的路径) "setupEntry": "dist/setup-entry.js", // 极其轻量级的 UI 配置入口 "install": { "minHostVersion": ">=2026.3.22" // 类似 Maven provided,声明兼容的网关底线版本 } }}
🛠️ 第三章:核心开发规范与入门示例

3.1 严格的导包规范 (Import Conventions – 避坑核心)
🚫 致命错误: 在 Java 中我们习惯 import com.example.core.*。但在 OpenClaw,绝对不要引入单体大包(如直接 import 根目录的 openclaw/plugin-sdk/)。这会导致极其严重的循环依赖和启动缓慢。
✅ 最佳实践: 必须精确到细粒度的子路径。
// ✅ 正确:需要构建入口时import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';// ✅ 正确:需要使用类型 Schema 时import { Type } from 'openclaw/plugin-sdk/types';
3.2 注册模式感知 (Registration Modes)
你的 register(api) 方法类似于 Spring 的 ApplicationListener。网关会在不同阶段通过 api.registrationMode 传入不同状态:
full(全量模式):真正的完全启动。此时你可以建立数据库连接池、启动后台定时任务、注册工具。
discovery(发现模式):只读探测模式。网关只是为了收集信息以构建注册表快照。严禁在此模式下执行任何网络请求或 I/O 动作。
setup(配置模式):仅在冷启动验证或渲染配置界面时加载。
3.3 完整示例:开发一个 Echo Tool (复读机工具)
1. openclaw.plugin.json
{ "id": "my-plugin", "name": "openclaw-my-plugin", "description": "为 OpenClaw 添加一个自定义工具", "contracts": { "tools": ["my-plugin"] }, "configSchema": { "type": "object", "additionalProperties":false }}
2. package.json
{ "name": "@myorg/openclaw-my-plugin", "version": "1.0.0", "type": "module", "scripts": { "test": "vitest run" }, "openclaw": { "extensions": [ "./src/index.ts" ], "compat": { "pluginApi": ">=2026.3.24-beta.2", "minGatewayVersion": "2026.3.24-beta.2" }, "build": { "openclawVersion": "2026.3.24-beta.2", "pluginSdkVersion": "2026.3.24-beta.2" } }, "dependencies": { "@sinclair/typebox": "^0.34.49" }, "devDependencies": { "openclaw": "^2026.4.29", "vitest": "^4.1.5", "@tsconfig/node24": "^24.0.4", "@types/node": "^25.6.0", "typescript": "^6.0.3" }}
3. src/index.ts
// 严格按照规范细粒度导包import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";import { Type } from "@sinclair/typebox";export default definePluginEntry({ id: "my-plugin", name: "openclaw-my-plugin", description: "为 OpenClaw 添加一个echo工具", register(api) { api.logger.info(`[EchoPlugin] 启动,当前模式: ${api.registrationMode}`); // 【防御性编程】任何有副作用或真正业务的注册,必须隔离在 full 模式下 if (api.registrationMode === 'full') { api.registerTool({ name: "my_echo", description: "Do echo 回声", parameters: Type.Object({ input: Type.String() }), async execute(_id, params) { api.logger.info(`[my-my_echo] LLM 调用了复读机,输入: ${params.input}`); return { content: [{ type: "text", text: `Got: ${params.input}` }] }; }, }); } },});
3.4 闭环体验:安装、配置与触发 (How to Run)
写完代码后,如何将你的工具/拦截器装载到 OpenClaw 中并触发它?
1. 编译并安装插件
# 1. 在你的插件项目目录下编译 TS 到 dist/npm run build# 2. 告诉 OpenClaw 安装此本地插件(系统会自动过滤有害的安装脚本)openclaw plugins install .
2. 在全局配置中启用 修改 ~/.openclaw/openclaw.json:
{ "plugins": { "enabled":true, "entries": { "my-plugin": { "enabled":true } } }}
⚔️ 第四章:实战场景深度剖析
4.1 场景一:开发一个 Agent Tool(内部 API 调用工具)
这是最常见的需求:让大模型(LLM)能调用你公司内部的 Java API。
api.runtime.tools.registerTool({ id: 'my-corp-order-query', name: 'query_user_order', description: '当用户询问他们的订单状态时,使用此工具查询数据库。', schema: Type.Object({ orderId: Type.String({ description: '用户的订单编号' }) }), run: async (args, context) => { // 通过 context 获取发起人信息进行审计或鉴权 api.logger.info(`正在查询订单: ${args.orderId}, 发起人: ${context.user}`); // 实际的业务网络请求 const response = await fetch(`http://internal-java-service/api/orders/${args.orderId}`); const data = await response.json(); // 返回文本格式供 LLM 分析 return { text: JSON.stringify(data) }; }});
重启网关并在聊天窗口验证。对 Agent 说:”帮我查一下订单编号 ORD-9988 的状态”。Agent 将自动寻找 query_user_order,执行,并告诉你结果。
4.2 场景二:开发全局 Hook(AOP 切面审批流)
假设你需要实现一个风控需求:任何发往外部的敏感系统指令,必须经过人类管理员审批。核心就是利用 OpenClaw 类似 @Around 的机制注册一个全局生命周期拦截器:
// 完美等价于 Java AOP 中的 @Around 切面拦截api.runtime.hooks.register('before_tool_call', { id: 'drop-db-protector', priority: 100, // 优先级排序 handler: async (event, context) => { // 拦截高危操作:解析即将执行的命令 if (event.toolName === 'exec' && event.args?.command?.includes('drop database')) { api.logger.warn(`拦截到高危操作!用户: ${context.user}`); // 挂起 Agent 线程,并在聊天窗口中弹出要求人类审批的 UI 卡片 return { requireApproval: true }; } // 正常操作予以放行 return { block: false }; }});
重启网关并在聊天窗口验证。对 Agent 说:”帮我执行系统命令:drop database test”。此时,Agent 试图调用 exec,但你的屏幕上会立刻弹出一个 需要人类审批 (Require Approval) 的确认卡片,点击同意后方可执行。
4.3 核心生命周期钩子 (Hooks) 概览
在上述场景中,演示了 before_tool_call 钩子的使用。应对更复杂的业务场景,这里列出 OpenClaw 插件系统中几个最关键的生命周期钩子(你可以把它们等同于 Spring Framework 里的 HandlerInterceptor 或特定阶段的 AOP 切面):
before_tool_call:工具执行前置拦截。正如 4.2 所示,当你返回 { block: true } 或 { requireApproval: true } 时,可以强行阻断工具运行,是实现权限校验、高危指令人类审批的绝佳位置。
after_tool_call:工具执行后置监听(类似于 Java 的 @AfterReturning)。此钩子不可用于阻断执行,但可以获取工具的最终执行结果。非常适合用于业务操作审计、数据收集与执行日志落盘。
message_sending:消息发送前拦截。在大模型生成了回复,准备发送到前端聊天界面(如 Telegram/Discord)的最后一刻触发。如果返回 { cancel: true } 则中止发送。这是做全局敏感词过滤、机密数据脱敏替换的最佳切面。
before_model_resolve / before_prompt_build:在 Agent 即将组装 System Prompt 发送给底层大模型引擎之前的拦截点。你可以利用它向 Prompt 中动态注入公司特有的上下文、用户权限信息,或者强制指定将要路由使用的模型。
before_install:插件安装前拦截。允许你在系统层面拒绝安装某些不符合安全规范的第三方插件(返回 { block: true } 将产生终端拒绝行为)。
🧠 第五章:强大的运行时助手 (api.runtime.*)
OpenClaw SDK 提供了 api.runtime 对象,这是网关暴露给你的”系统级工具箱”。不要把这些对象看作是死板的接口,我们可以按照 你在实际业务中可能遇到的应用场景 来理解它们的作用:
场景一:赋予大模型全新的手脚(扩充动作)
如何做:使用 api.runtime.tools.registerTool()。
用途:当你想让大模型具备查公司数据库、发邮件、控制智能家居等能力时,向网关注册自定义函数供其调用。
场景二:替换或增加底层的大脑(接入私有大模型)
如何做:使用 api.runtime.providers.registerProvider()。
用途:公司内网部署了本地模型,你想让 OpenClaw 使用它来进行对话和思考。
场景三:掌控全局安全与审计生命周期(AOP 切面控制)
如何做:使用 api.runtime.hooks.register()。
用途:你想在 Agent 每次发消息前做敏感词替换,或者在调用某个高危工具前强行阻断并要求人类点击”同意”按钮。
场景四:复用官方强大的视觉与语音能力
如何做:调用 api.runtime.mediaUnderstanding 等子模块。
用途:你的插件收到了一张图片并需要知道图片里的文字,你无需自己编写 OCR 代码或对接外部大模型,直接调用该模块白嫖系统底层的多模态识别能力。
场景五:执行耗时任务而不卡死聊天窗口
如何做:调用 api.runtime.subagent.runEmbeddedAgent()。
用途:你需要让大模型去阅读并总结一个包含 100 页 PDF 的财报,这可能需要 5 分钟。为了不阻塞主对话流,通过此模块在后台派生一个”异步子代理 (Sub-agent)”,完成后再推送到前端。
场景六:动态修改网关的底层配置
如何做:调用 api.runtime.config.patch()。
用途:你开发了一个后台管理 Web UI,用户在页面上填入了新的 API Key,你可以直接调用此方法将新配置热更新到底层引擎,甚至触发无感重启。
🛡️ 第六章:测试
契约单元测试 (Vitest) 流程
OpenClaw 官方推荐使用 Vitest 进行测试。核心痛点在于:如何 Mock 庞大的 api.runtime 而不污染全局原型?官方提供了一个完美的测试助手。
测试对象:我们在 3.3 节编写的 Echo Tool
完整测试代码 (tests/index.test.ts)
import { describe, it, expect, vi } from "vitest";import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api"; // 使用 SDK 提供的 mock 工具 [4]import plugin from "../src/index.js"; // 导入你的插件入口describe("my-plugin", () => { it("应该正确注册 my_echo 工具", async () => { // 1. 创建一个模拟的 API 对象 [5] const mockApi = createTestPluginApi({ registrationMode: "full" // 模拟完整注册模式 [6] }); // 2. 使用 vi.fn() 监控 registerTool 方法 [2] mockApi.runtime.tools.registerTool = vi.fn(); // 3. 执行插件的注册逻辑 plugin.register(mockApi); // 4. 断言:验证 registerTool 是否被调用 [3] expect(mockApi.runtime.tools.registerTool).toHaveBeenCalledWith( expect.objectContaining({ name: "echo", description: "一个复读机工具,将输入文本原样返回。", }) ); }); it("echo 工具的 run 方法应该返回正确的文本", async () => { // 1. 准备一个变量来捕获注册的工具逻辑 let registeredTool: any; const mockApi = createTestPluginApi({ registrationMode: "full" }); // 2. 拦截注册调用并保存工具定义 mockApi.runtime.tools.registerTool = vi.fn((tool) => { registeredTool = tool; }); plugin.register(mockApi); // 3. 测试 run 核心逻辑 const testInput = "Hello OpenClaw!"; const result = await registeredTool.run({ text: testInput }, { user: 'tester' }); // 4. 断言:验证输出格式和内容 [7] expect(result).toEqual({ text: `Got: ${testInput}` }); });});
运行命令:在项目根目录执行 npx vitest run 即可快速验证你的插件契约。
📦 第七章:发布到 NPM 社区
当你的插件在本地测试通过,准备分享给整个开源生态或团队使用时,你需要将其发布到 NPM 仓库。具体发布与安装步骤如下:
1. 检查插件配置信息
打开项目根目录下的 package.json,确保以下字段正确:
移除私有属性:确保配置中不包含 "private": true,或者显式将其修改为 "private": false。
版本管理:确保 "version" 字段符合你此次发布的版本规划(例如 1.0.0)。
2. 登录 NPM 镜像库
在终端执行以下命令,根据提示输入你的 npm 账号、密码及邮箱完成身份认证:
npm login --registry https://registry.npmjs.org
3. 执行公开发布
执行发布命令,注意显式指明 public 访问权限以防某些私有域校验报错:
npm publish --access public --registry https://registry.npmjs.org
4. 社区用户安装与使用
发布成功后,全球的 OpenClaw 用户(或者你自己在生产服务器上)只需执行以下一条命令,系统会自动拉取该包并完成依赖解析安装:
openclaw plugins install <你的包名>
🧩 第八章:生态铁三角 —— Plugin、Tool 与 Skill 的协作哲学
在 OpenClaw 的架构设计中,Plugin (插件)、Tool (工具) 和 Skill (技能) 是三个高频出现且容易混淆的核心概念。作为 Java 开发者,如果你能理清这三者的关系,你就能设计出真正具备高内聚力、业务闭环的企业级扩展。

8.1 三者的边界与职责 (Java 架构映射)
Plugin(插件)= Spring Boot Starter (基础设施与生命周期包装)Plugin 是一段 Node.js/TypeScript 代码工程。它负责处理底层的网络通讯、依赖注入、向网关注册能力,以及捕获生命周期事件(Hooks)。它是一个载体,自己本身不直接为大模型提供对话功能。
特征:需要 npm install,需要编译构建,写在 src/index.ts 中。
Tool(工具)= @Service 暴露的 RPC 方法 (具体的 API 能力)Tool 是被装载在 Plugin 内部的可执行代码片段。你通过 api.registerTool 向系统暴露它,它拥有明确的输入输出参数 Schema。大模型 (LLM) 通过 JSON 函数调用(Function Calling)来执行它。
特征:大模型的”手和脚”。比如:发邮件工具、执行 SQL 工具、截屏工具。
Skill(技能)= 业务流程脚本 / Drools 规则引擎 (提示词与说明书)Skill 是一组以 SKILL.md (Markdown) 格式编写的自然语言指令和文档。大模型虽然有了发邮件的 Tool,但它不知道”何时该发、发给谁、语气如何”。Skill 就是把这些业务规则写下来,动态注入到系统提示词 (System Prompt) 中。
特征:无需编译,纯文本/Markdown驱动,热更新生效。它是大模型的”业务说明书”。
总结流转链条:Plugin 负责注册 Tool(造出锤子),但大模型拿到锤子可能会乱敲;此时你需要写一个 Skill(说明书),告诉大模型”遇到钉子时才用锤子,且力度要适中”。
8.2 高阶进阶:在插件中”打包”技能 (Plugin Bundles)
痛点场景:你开发了一个”企业内部 Gitlab 操作”的插件(Plugin),里面包含了查询 MR、合并代码的 Tools。如果你把插件单独发布,用户安装后,大模型面对这些陌生的 Tool 往往表现得像个无头苍蝇,不知道按什么业务规范来合并代码。
解决方案:Plugin Bundles (自带技能库的插件)
OpenClaw 允许你在插件项目中直接引入并随包附带 (Bundle) 相关的 Skills。这样,当用户执行 openclaw plugins install 安装你的插件时,底层的 API Tools 和顶层的业务指导脚本 (Skills) 会被同时加载到系统中。
核心实现方式
你只需要在插件项目的根目录下,创建一个特殊的 skills/ 文件夹。
目录结构:
my-gitlab-plugin/├── package.json├── openclaw.plugin.json├── src/│ └── index.ts # 在这里通过 api.runtime.tools 注册 gitlab_merge 等工具└── skills/ # 💡 新增技能目录 └── code-reviewer/ # 你的专属技能包 └── SKILL.md # 告诉大模型如何使用你的 gitlab 工具
安全与隔离机制 (Security)
作为开发者,你需要了解 Bundle Skill 的安全边界:
边界检查:插件打包的 Skills 其文件路径严格受限于插件根目录,无法越权读取系统外的配置。
依赖复用:这种模式完美实现了“API与Prompt的闭环交付”,插件不仅仅在提供冷冰冰的代码,更赋予了大模型开箱即用的专业领域认知(Domain Knowledge)。
🏆 第九章:从零落地完整 基于云雾API的 OpenClaw 原生生图插件
插件地址:https://github.com/dirtydamn1/openclaw-yunwu-nano-banana
使用云雾Api提供商的NanoBanana模型,以降低生图成本。
在第四章中,我们演示了如何注册一个普通的 Tool(工具)。但对于大模型生图(Image Generation)、语音合成(TTS)这类核心能力,OpenClaw 提供了更深层次的 原生 Provider 扩展机制。
作为 Java 开发者,你可以这样理解:注册一个普通的 Tool 就像是写一个 @RestController 供大模型调用;而注册一个 ImageGenerationProvider 则像是在实现系统底层的 SPI(Service Provider Interface)或注入一个核心的 Strategy 策略 Bean。网关会自动接管所有关于”尺寸、长宽比、生成和编辑”的标准协议,将用户请求精准路由给你的实现。

9.1 核心架构解析
开发此类 Provider 插件通常包含两个核心部分:
统一入口层:使用 api.registerImageGenerationProvider 向网关注册这个能力提供者。
SPI 实现层:返回一个符合 ImageGenerationProviderPlugin 契约的对象,声明该服务支持的模型(models)、能力集(capabilities)并实现具体的 generateImage 业务逻辑。
9.2 主入口代码 (src/index.ts)
这是非常典型的声明式入口,通过依赖注入的形式,将我们自定义的 Provider 挂载到 OpenClaw 网关的生图管线中:
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";import { buildImageGenerationProvider } from "./generate/image-generation.js";export default definePluginEntry({ id: "yunwu-nano-banana", name: "YunWu Nano Banana", description: "OpenClaw native image generation plugin based on YunWu API NanoBanana", register(api) { // 核心动作:不是注册普通的 Tool,而是注册原生的生图 Provider (SPI 实现) api.registerImageGenerationProvider(buildImageGenerationProvider(api)); }});
9.3 核心生图 Provider 实现 (src/generate/image-generation.ts)
在这个文件中,我们实现了真正的业务逻辑。注意几个优雅的 SDK 运用:
提取配置:从 req.cfg 中精准拿到用户在 openclaw.json 里配置的专属 apiKey。
使用原生 HTTP 客户端:使用了 openclaw/plugin-sdk 内置的 postJsonRequest,而不是原生的 fetch,这自带了超时控制、连接复用和内存释放 (release) 能力。
数据流适配:将第三方 API 返回的 Base64 包装成了 OpenClaw 内核所需的 Buffer 格式返回。
以下是精简后的核心实现代码:
import { ImageGenerationProviderPlugin, OpenClawPluginApi } from 'openclaw/plugin-sdk/plugin-runtime'import { postJsonRequest } from 'openclaw/plugin-sdk/provider-http'import { ImageGenerationRequest, ImageGenerationResult } from 'openclaw/plugin-sdk/image-generation'const DEFAULT_MODEL = "gemini-3.1-flash-image-preview";const DEFAULT_OUTPUT_MIME = "image/png";// 构建 SPI 具体实现对象export function buildImageGenerationProvider(api: OpenClawPluginApi): ImageGenerationProviderPlugin { return { id: "yunwu-nano-banana", label: "YunWu API Nano Banana", defaultModel: DEFAULT_MODEL, models: [DEFAULT_MODEL], // 状态检查:判断用户是否已经正确配置了当前 Provider isConfigured: ({ agentDir }) => true, // 契约声明:向网关汇报当前 Provider 支持的能力范围(尺寸、清晰度、图片编辑等) capabilities: { generate: { maxCount: 4, supportsSize: true, supportsAspectRatio: true, supportsResolution: true, }, // ...省略 edit 和 geometry 的声明... }, // 核心执行逻辑:当网关决定使用此 Provider 生图时调用 async generateImage(req: ImageGenerationRequest) { api.logger.info(`yunwu api generate image=${req.prompt}`); // 1. 获取全局配置文件中的专属插件配置 (API Key) let pluginConfig = req.cfg.plugins?.entries?.["yunwu-nano-banana"]?.config const apiKey = pluginConfig?.["apiKey"]; if (!apiKey) { throw new Error("Missing yunwu-nano-banana plugin configuration"); } const model = pluginConfig?.["model"] || DEFAULT_MODEL; // 2. 构造支持底图输入的能力 (Image-to-Image) const inputParts = (req.inputImages ?? []).map((image) => ({ inlineData: { mimeType: image.mimeType, data: image.buffer.toString("base64"), }, })); // 3. 调用 OpenClaw SDK 提供的原生 HTTP 工具发起请求,自带完善的资源释放和超时机制 const { response: res, release } = await postJsonRequest({ url: `https://yunwu.ai/v1beta/models/${model}:generateContent`, headers: new Headers({ "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` }), body: { contents: [{ role: "user", parts: [...inputParts, { text: req.prompt }] }], generationConfig: { responseModalities: ["IMAGE"] }, // 忽略参数转换细节 }, timeoutMs: req.timeoutMs ?? 120_000, fetchFn: fetch }); try { const payload = await res.json() as any; let imageIndex = 0; // 4. 解析结果:将云雾 API 响应的数据(Base64)映射为 OpenClaw 标准的 Buffer 数组 const images = (payload.candidates ?? []) .flatMap((candidate: any) => candidate.content?.parts ?? []) .map((part: any) => { const inline = part.inlineData ?? part.inline_data; const data = inline?.data?.trim(); if (!data) return null; const mimeType = inline?.mimeType ?? DEFAULT_OUTPUT_MIME; const extension = mimeType.split("/")[1] ?? "png"; imageIndex += 1; // 返回 SDK 标准的渲染结果 return { buffer: Buffer.from(data, "base64"), mimeType, fileName: `image-${imageIndex}.${extension}`, }; }) .filter(entry => entry !== null); if (images.length === 0) throw new Error("yunwu api missing image data"); // 5. 成功返回并由底层 Gateway 负责呈现到对应的沟通渠道(Discord/Telegram/Web) return { images, model } as ImageGenerationResult; } finally { // 释放底层连接 await release(); } } };}
🎉 结语:致 Java 开发者的 OpenClaw 漫游指南
读到这里,恭喜你!你已经打破了对 Node.js/TypeScript 插件生态的陌生感,深入到了 OpenClaw 插件开发的核心骨架之中。
回顾全文,我们其实只做了一件事:用 Java 架构设计的思维,重新拆解并掌握了 OpenClaw 插件开发。
让我们做最后一次核心知识点的心智映射总结:
全局静态约定 (Manifest):openclaw.plugin.json 完美对应了你的 spring.factories;而 configSchema 则是你的 @ConfigurationProperties,用于在冷启动阶段保障配置的强类型安全。
生命周期控制 (Lifecycle):register(api) 就是你的 ApplicationRunner 或 @PostConstruct 钩子。我们学习到了,任何涉及到网络或数据库的耗时操作,都必须被隔离在 registrationMode === ‘full’ 的环境隔离墙之后。
外挂能力扩充 (Tools):通过 api.runtime.tools.registerTool 开发内部 API 调用工具,这等价于你写了一个带有 @RestController 的 Web 接口,只不过现在路由的调用方变成了聪明的大语言模型(LLM)。
核心切面拦截 (Hooks):api.runtime.hooks.register 机制完全就是 AOP 中 @Around 的复刻。利用它,你能优雅地实现企业级的敏感词拦截,或是强制切入人工审批流(Require Approval)。
底层能力植入 (Providers):就像第八章我们所剖析的那样,当我们需要替换底层的引擎时,我们不再是写普通的接口,而是通过 api.registerImageGenerationProvider 实现了一个标准化的 SPI (Service Provider Interface) 策略组件。
生态与业务的融合 (Skills & Bundles):就像刚刚在第九章学习的,Plugin 提供底层的”骨与肉”(Tools),而 Skill 赋予其”业务灵魂”。通过 Plugin Bundles 机制,你能将这两者完美结合,交付具备完整上下文认知的解决方案。
高质量保障 (Testing):Vitest 加上系统内置的 createTestPluginApi 模拟环境,就像是你在用 Mockito 结合 @SpringBootTest。这让你能在不启动整个网关的情况下,在干净的沙箱里完成严谨的契约测试。
从手写一个极简的 Echo 复读机,到利用全局拦截器实现风控,再到优雅地实现第三方大模型的 SPI 接入,并在插件内嵌业务脚本打造生态闭环。你在后端领域积累的工程化思想和设计模式,在这个全新的生态里不仅不过时,反而正是构建优秀插件所需的最强武器。
打开你的 IDE,开启你的 OpenClaw 插件开发之旅吧!
本指南由 OpenClaw 创作,保留所有权利。
夜雨聆风