乐于分享
好东西不私藏

手搓一个OpenClaw免费图片插件,我只用了三个文件

手搓一个OpenClaw免费图片插件,我只用了三个文件

用 OpenClaw 也有一阵子了,这玩意儿确实好用,但有一个问题一直让我有点不爽——图片生成。

OpenClaw 自带的 image_generate 工具是个好东西,你直接在对话里说"帮我画一只猫",它就能调用 API 出图。但问题是,它默认走的 OpenAI 或者 OpenRouter,这些要么付费,要么配置起来一堆弯弯绕绕。

我就想,能不能白嫖一个免费的?

这时候我注意到了 Agnes AI——一个提供免费 API 的新平台。Agnes Image 2.1-Flash 和 2.0-Flash 都是免费的,生成质量还不错。但问题是,OpenClaw 不认识 Agnes 啊,我得自己把它接进去。

于是就有了这篇文章——给 OpenClaw 写一个图片生成插件,到底有多简单?

插件是什么?先打个比方

如果你没用过 OpenClaw 的插件系统,我先用大白话解释一下。

OpenClaw 本身是一个框架,它提供了各种能力——对话、工具调用、图片生成、视频生成等等。但具体怎么调用哪个 API、用哪个模型,它不知道。插件就是干这个的——告诉 OpenClaw"某某能力可以用某某 API 来实现"。

我写的 agnes-image-gen 插件,就是告诉 OpenClaw:"图片生成和视频生成,用 Agnes AI 的免费 API 就行。"

两条路:extensions 和 plugins

OpenClaw 有两种加载插件的方式:

方式
放哪
怎么加载
extensions/~/.openclaw/extensions/
自动扫描,放进去就行
plugins/~/.openclaw/plugins/
必须用 openclaw plugins install 安装

开发阶段建议用 extensions/,简单粗暴,改代码后重启就生效。

踩坑: 我第一次写的时候把插件放到了 plugins/ 目录,结果 OpenClaw 怎么都加载不了,折腾了半天才发现路径不对。血泪教训。

动手写一个:三个文件搞定

一个最简的插件只需要三个文件:

extensions/agnes-image-gen/├── openclaw.plugin.json    # 插件身份证├── package.json            # 包信息└── index.js                # 插件逻辑

第一步:manifest(插件身份证)

openclaw.plugin.json 是 OpenClaw 读取插件信息的入口,在加载你的代码之前就会读取它:

{  "id": "agnes-image-gen",  "name": "Agnes Image Generation",  "description": "Adds Agnes-Image-2.0-Flash + Agnes-Video-V2.0 (free API)",  "contracts": {    "imageGenerationProviders": ["agnes"],    "videoGenerationProviders": ["agnes"]  },  "activation": {    "onStartup":true  },  "configSchema": {    "type": "object",    "additionalProperties":false  }}

这里有几个关键字段:

  • • id — 插件的唯一标识,相当于身份证号。后面配置的时候也要用这个 id。
  • • contracts — 声明这个插件提供了什么能力。我声明了 imageGenerationProviders 和 videoGenerationProviders,id 都叫 "agnes"。意思是"图片和视频生成,找我就行"。
  • • activation.onStartup — 设为 true 表示随 OpenClaw 启动自动加载。

第二步:入口文件(核心逻辑)

index.js 是实际干活的地方。入口用 definePluginEntry 定义:

import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";export default definePluginEntry({  id: "agnes-image-gen",  name: "Agnes Image Generation",  description: "Agnes-Image-2.0-Flash + 2.1-Flash 图片生成 (免费API)",  register(api) {    // 在这里注册 provider  },});

核心就是 register(api) 这个函数——在这里面调用 api.registerImageGenerationProvider(...) 或 api.registerVideoGenerationProvider(...),把你的 provider 注册进去。

第三步:注册图片生成 provider

api.registerImageGenerationProvider({  id: "agnes",  label: "Agnes AI",  defaultModel: "agnes-image-2.1-flash",  models: ["agnes-image-2.0-flash", "agnes-image-2.1-flash"],  // 检查是否配置了 API Key  isConfigured: ({ cfg }) => !!resolveApiKey(cfg),  async generateImage(req) {    // 在这里调用 Agnes API    const response = await fetch(`${baseUrl}/images/generations`, {      method: "POST",      headers: {        "Authorization": `Bearer ${apiKey}`,        "Content-Type": "application/json",      },      body: JSON.stringify({        model: req.model || "agnes-image-2.1-flash",        prompt: req.prompt,        size: req.size || "1024x768",        extra_body: { response_format: "url" },      }),    });    // ... 处理返回数据  },});

generateImage(req) 是 OpenClaw 框架调用的方法,你只需要在里面调 API、拿结果、返回。框架负责把结果发给 agent,agent 再呈现给用户。

第四步:处理 Agnes API 的特殊性

Agnes 的 API 和 OpenAI 标准格式有一点不同——它不支持返回 base64 编码的图片,只返回图片的 URL。

所以需要在插件里多加一步:拿到 URL 后,fetch 下载回来,转成 buffer 再返回给框架。

async function downloadImage(imageUrl) {  const response = await fetch(imageUrl, {    signal: AbortSignal.timeout(60_000),  });  if (!response.ok) return null;  const buffer = Buffer.from(await response.arrayBuffer());  // 根据文件头判断格式  let mimeType = "image/png";  if (buffer[0] === 0xff && buffer[1] === 0xd8) {    mimeType = "image/jpeg";  } else if (buffer.toString("ascii", 0, 4) === "RIFF") {    mimeType = "image/webp";  }  return { buffer, mimeType };}

第五步:顺便把视频生成也加上

Agnes 还提供了免费的 agnes-video-v2.0 视频模型,我就顺手也加上了。视频生成比图片复杂一些——它是异步任务,需要创建任务 → 轮询结果 → 下载视频:

api.registerVideoGenerationProvider({  id: "agnes",  label: "Agnes AI",  defaultModel: "agnes-video-v2.0",  defaultTimeoutMs: 900_000,  // 15分钟超时  async generateVideo(req) {    // 1. 创建视频任务    const createResp = await fetch(`${baseUrl}/videos`, {      method: "POST",      headers: { "Authorization": `Bearer ${apiKey}` },      body: JSON.stringify({        model: "agnes-video-v2.0",        prompt: req.prompt,        num_frames: 121,        frame_rate: 24,        width: 1152,        height: 768,      }),    });    const taskData = await createResp.json();    // 2. 轮询等待完成    while (Date.now() < deadline) {      await new Promise(r => setTimeout(r, 15000));      const statusResp = await fetch(pollUrl, {        headers: { "Authorization": `Bearer ${apiKey}` },      });      const result = await statusResp.json();      if (result.status === "completed") {        // 3. 下载视频        const videoResp = await fetch(result.remixed_from_video_id);        const buffer = Buffer.from(await videoResp.arrayBuffer());        return { videos: [{ buffer, mimeType: "video/mp4" }] };      }    }  },});

配置一下就能用了

插件写好后,重启 OpenClaw,然后在 openclaw.json 里把它设为默认的图片生成模型:

{  "agents": {    "defaults": {      "imageGenerationModel": {        "primary": "agnes/agnes-image-2.1-flash",        "fallbacks": [          "sensenova/sensenova-u1-fast"        ]      }    }  }}

API Key 可以通过环境变量 AGNES_API_KEY 配置,或者在 openclaw.json 里写:

{  "models": {    "providers": {      "agnes": {        "apiKey": "你的API_KEY",        "baseUrl": "https://apihub.agnes-ai.cn/v1"      }    }  }}

配置好之后,在对话里直接说"帮我画一只穿着西装的猫"或者"生成一段海边的视频",OpenClaw 就会调用 Agnes 的免费 API 来生成。

踩坑记录:这些坑我替你踩过了

坑1:插件放错位置

问题: 放 plugins/ 目录下不加载。解决方案: 放 extensions/ 下。plugins/ 只能通过 openclaw plugins install 安装,手动放不会被扫描到。

坑2:Agnes 不支持 base64

问题: Agnes API 只返回 URL,不返回 base64 图片数据。OpenClaw 默认期望的是 base64。解决方案: 在插件里多一步——下载 URL 转 buffer。

坑3:视频轮询接口偶发连接重置

问题: 用 GET /v1/videos/{taskId} 轮询时,时不时 TLS 握手失败。解决方案: 改用 GET /agnesapi?video_id={video_id} 接口,这个接口更稳定。

坑4:视频URL藏在奇怪的字段里

问题: 任务完成后,返回的数据里视频 URL 不在常见的 url 或 output.url 字段,而是藏在 remixed_from_video_id 里。解决方案: 多看两眼返回数据,不要把字段名写死,多试几个可能性。

坑5:轮询时的网络抖动

问题: 轮询过程中偶发 TypeError(连接重置),一次失败就导致整个 Promise reject。解决方案: 加 try-catch 和重试逻辑,连续失败 10 次才放弃:

let consecutiveErrors = 0;while (Date.now() < deadline) {  await new Promise(r => setTimeout(r, 15000));  try {    const resp = await fetch(pollUrl, { /* ... */ });    // 处理成功情况    consecutiveErrors = 0;  } catch (err) {    consecutiveErrors++;    if (consecutiveErrors >= 10) throw err;    // 否则继续重试  }}

写完之后的感觉

说实话,写完这个插件最大的感受不是"我好牛",而是"原来 OpenClaw 的插件系统设计得挺清晰的"。

你不需要理解 OpenClaw 的底层实现,不需要改它的源码,只需要写一个符合规范的插件,它就能自动发现、自动加载。manifest 声明能力,入口文件实现逻辑,框架负责调度。

而且最爽的是——现在我可以在对话里直接让 OpenClaw 生成图片和视频,完全免费。 输入"帮我画一张赛博朋克风格的城市夜景",等几秒钟,图就出来了。

如果你也在用 OpenClaw,不妨也试试自己写个插件——从图片生成这种单一能力入手,门槛其实很低。


附:插件完整源码

点阅读原文提取。

以上,如果觉得不错,随手点个赞、在看、转发三连吧,如果想第一时间收到推送,也可以给我个星标⭐~谢谢你看我的文章,我们,下次再见

本文基于 OpenClaw 2026.7.1 版本

我的Openclaw教程


OpenClaw 🦞 详细的小白安装教程及避坑指南
OpenClaw小白多模型设置教程及避坑指南
今天起,无限期免费!全球首个全模态API开放,我已经替你测试过了,OpenClaw可用!
OpenClaw 🦞  商汤日日新原生多模态智能体模型,Token Plan 限免中,5小时1500次调用。
OpenClaw Workboard工作板教程,让你的AI自己领任务干活
OpenClaw Skill Workshop 技能工坊教程
OpenClaw 🦞  50+ 个官方内置 Skills 完全指南
OpenClaw 🦞  100+个内置扩展插件plugin完全指南
OpenClaw 记忆搜索配置教程,让AI不再失忆
OpenClaw iOS APP 使用教程:把手机变成AI的「眼睛和耳朵」