OpenClaw 插件架构

2026-03-20 14:20 更新

OpenClaw插件架构指南(完整翻译版)

本文档讲解 OpenClaw 插件系统的内部架构。如需了解面向用户的插件安装、发现与配置相关内容,请查看插件文档。

公共能力模型

能力是 OpenClaw 内部的原生插件公共模型。每个原生 OpenClaw 插件都可以注册一个或多个能力类型:

能力类型 注册方法 示例插件
文本推理 api.registerProvider(...) openaianthropic
语音 api.registerSpeechProvider(...) elevenlabsmicrosoft
媒体理解 api.registerMediaUnderstandingProvider(...) openaigoogle
图像生成 api.registerImageGenerationProvider(...) openaigoogle
网页搜索 api.registerWebSearchProvider(...) google
通道 / 消息 api.registerChannel(...) msteamsmatrix

如果一个插件没有注册任何能力,仅提供钩子、工具或服务,那么它属于仅旧版钩子插件。该模式目前仍被完全支持。

外部兼容性策略

能力模型已在核心层落地,目前已被内置 / 原生插件使用,但外部插件的兼容性需要比 “只要导出了,就代表是稳定的” 更严格的标准。

当前的指导原则:

  • 现有外部插件:保持基于钩子的集成可用,将其作为兼容性基线

  • 新的内置 / 原生插件:优先使用显式的能力注册,而非厂商专属的侵入式逻辑,或是新的仅钩子设计

  • 采用能力注册的外部插件:允许使用,但能力专属的辅助接口仍处于迭代中,除非文档明确标记该契约为稳定版本

实用规则:

  • 能力注册 API 是我们的目标方向

  • 在过渡阶段,旧版钩子仍是外部插件最安全的无破坏性变更路径

  • 导出的辅助子路径并非全部等价;请优先使用文档中明确标注的稳定契约,而非临时的辅助导出

插件形态

OpenClaw 会根据插件实际的注册行为(而非仅静态元数据),将每个加载的插件划分为不同的形态:

  • 单能力插件 — 仅注册一种能力类型(例如仅提供模型提供商的 mistral 插件)

  • 混合能力插件 — 注册多种能力类型(例如 openai 插件同时提供文本推理、语音、媒体理解和图像生成能力)

  • 仅钩子插件 — 仅注册钩子(类型化或自定义),不包含能力、工具、命令或服务

  • 非能力插件 — 注册工具、命令、服务或路由,但不包含能力

你可以使用 openclaw plugins inspect <id> 命令查看插件的形态和能力拆分,详情请查看CLI 参考

旧版钩子

before_agent_start 钩子目前仍作为仅钩子插件的兼容路径被支持,不少线上的旧版插件仍依赖它。我们的规划:

  • 保持该钩子可用

  • 将其标记为旧版并写入文档

  • 对于模型 / 提供商的覆写逻辑,优先使用 before_model_resolve

  • 对于提示词修改逻辑,优先使用 before_prompt_build

  • 仅当实际的使用量下降,且测试用例覆盖证明迁移安全后,才会移除该钩子

兼容性标识

当你运行 openclaw doctoropenclaw plugins inspect <id> 时,可能会看到以下标识:

标识 含义
配置有效 配置解析正常,插件可正常加载
兼容性提示 插件使用了受支持但较旧的模式(例如 hook-only 形态)
旧版警告 插件使用了已废弃的 before_agent_start 钩子
严重错误 配置无效,或插件加载失败

hook-only 形态和 before_agent_start 钩子目前都不会导致你的插件无法运行 — hook-only 仅为提示,before_agent_start 仅会触发警告。这些标识也会出现在 openclaw status --allopenclaw plugins doctor 命令中。

架构总览

OpenClaw 的插件系统分为四层:

  1. 清单与发现:OpenClaw 从配置路径、工作区根目录、全局扩展根目录和内置扩展中查找候选插件。发现阶段会优先读取原生的 openclaw.plugin.json 清单,以及受支持的包清单。

  1. 启用与校验:核心层决定已发现的插件是启用、禁用、阻塞,还是被选中用于独占插槽(例如内存模块)。

  1. 运行时加载:原生 OpenClaw 插件会通过 jiti 在进程内加载,并将能力注册到中央注册中心。兼容的包会被标准化为注册中心记录,无需导入运行时代码。

  1. 消费层:OpenClaw 的其他模块会读取注册中心,来暴露工具、通道、提供商配置、钩子、HTTP 路由、CLI 命令和服务。

核心的设计边界:

  • 发现与配置校验应仅基于清单 / Schema 元数据完成,无需执行插件代码

  • 原生运行时行为来自插件模块的 register(api) 入口

这种拆分让 OpenClaw 可以在完整运行时启动前,就完成配置校验、说明缺失 / 禁用的插件,以及构建 UI/Schema 提示。

通道插件与共享消息工具

通道插件无需为常规的聊天操作单独注册发送、编辑、反应类工具。OpenClaw 在核心层维护了一个共享的 message 工具,通道插件仅需负责该工具背后的通道专属发现与执行逻辑。

当前的职责边界:

  • 核心层负责共享的 message 工具宿主、提示词关联、会话 / 线程记账,以及执行分发

  • 通道插件负责范围化的动作发现、能力发现,以及任意通道专属的 Schema 片段

  • 通道插件通过它们的动作适配器来执行最终的动作

对于通道插件,SDK 提供的接口是 ChannelMessageActionAdapter.describeMessageTool(...)。这个统一的发现调用可以让插件一次性返回它的可见动作、能力和 Schema 贡献,避免这些内容出现不一致。

核心层会在发现步骤中传入运行时范围,重要的字段包括:

  • accountId

  • currentChannelId

  • currentThreadTs

  • currentMessageId

  • sessionKey

  • sessionId

  • agentId

  • 可信的入站 requesterSenderId

这对上下文敏感的插件非常重要。通道可以根据当前的账户、当前的房间 / 线程 / 消息,或是可信的请求者身份,来隐藏或暴露消息动作,而无需在核心的 message 工具中硬编码通道专属的分支逻辑。

这也是为什么嵌入式运行器的路由变更仍属于插件的工作:运行器需要负责将当前的聊天 / 会话身份转发到插件的发现边界,这样共享的 message 工具才能为当前轮次暴露正确的、由通道负责的接口。

对于通道专属的执行辅助工具,内置插件应将执行运行时保存在自己的扩展模块中。核心层不再在 src/agents/tools 下维护 Discord、Slack、Telegram 或 WhatsApp 的消息动作运行时。我们不会发布独立的 plugin-sdk/*-action-runtime 子路径,内置插件应直接从自己的扩展模块导入本地的运行时代码。

针对投票功能,目前有两个执行路径:

  • outbound.sendPoll 是适配通用投票模型的通道的共享基线

  • actions.handleAction("poll") 是通道专属投票语义或额外投票参数的首选路径

核心层现在会在插件投票处理拒绝该动作后,才执行共享的投票解析,这样插件专属的投票处理器可以接受通道专属的投票字段,而不会被通用的投票解析器提前阻塞。 如需了解完整的启动流程,请查看加载流水线

能力所有权模型

OpenClaw 将原生插件视为厂商功能的所有权边界,而非一堆无关集成的集合。

这意味着:

  • 厂商插件通常应拥有该厂商所有面向 OpenClaw 的接口

  • 功能插件通常应拥有它所引入的完整功能接口

  • 通道应消费共享的核心能力,而非临时重实现提供商的行为

示例:

  • 内置的 openai 插件拥有 OpenAI 的模型提供商行为,以及 OpenAI 的语音、媒体理解和图像生成行为

  • 内置的 elevenlabs 插件拥有 ElevenLabs 的语音行为

  • 内置的 microsoft 插件拥有 Microsoft 的语音行为

  • 内置的 google 插件拥有 Google 的模型提供商行为,以及 Google 的媒体理解、图像生成和网页搜索行为

  • 内置的 minimaxmistralmoonshotzai 插件拥有它们各自的媒体理解后端

  • voice-call 插件是一个功能插件:它拥有通话传输、工具、CLI、路由和运行时,但它会消费核心的 TTS/STT 能力,而非重新实现一套语音栈

我们的目标状态是:

  • OpenAI 的所有能力都放在一个插件中,哪怕它覆盖了文本模型、语音、图像和未来的视频能力

  • 其他厂商也可以对自己的接口做同样的处理

  • 通道无需关心哪个厂商插件拥有该能力;它们只需消费核心层暴露的共享能力契约

这是核心的区别:

  • 插件 = 所有权边界

  • 能力 = 可被多个插件实现或消费的核心契约

所以当 OpenClaw 新增一个领域,比如视频,我们首先要问的不是 “哪个提供商应该硬编码视频处理逻辑?”,而是 “核心的视频能力契约是什么?”。一旦契约定义完成,厂商插件就可以基于它注册实现,通道 / 功能插件就可以消费它。

如果能力还不存在,正确的做法通常是:

  1. 在核心层定义缺失的能力

  1. 通过插件 API / 运行时以类型化的方式暴露它

  1. 将通道 / 功能与该能力关联

  1. 让厂商插件注册对应的实现

这可以让所有权明确,同时避免核心逻辑依赖单个厂商,或是一次性的插件专属代码路径。

能力分层

在决定代码归属时,可以参考这个心智模型:

  • 核心能力层:共享的编排、策略、降级、配置合并规则、交付语义,以及类型化契约

  • 厂商插件层:厂商专属的 API、鉴权、模型目录、语音合成、图像生成、未来的视频后端、用量查询端点

  • 通道 / 功能插件层:Slack/Discord/ 语音通话等集成,消费核心能力并将其暴露在对应接口上

例如,TTS 就遵循这个模式:

  • 核心层负责回复阶段的 TTS 策略、降级顺序、偏好设置,以及通道交付

  • openaielevenlabsmicrosoft 负责合成实现

  • voice-call 消费电话 TTS 运行时辅助工具

未来的能力也应优先遵循这个模式。

多能力厂商插件示例

从外部来看,厂商插件应该是内聚的。如果 OpenClaw 为模型、语音、媒体理解和网页搜索提供了共享契约,厂商就可以在一个地方拥有它所有的接口:



import type { OpenClawPluginDefinition } from "openclaw/plugin-sdk";
import {
 buildOpenAISpeechProvider,
 createPluginBackedWebSearchProvider,
 describeImageWithModel,
 transcribeOpenAiCompatibleAudio,
} from "openclaw/plugin-sdk";


const plugin: OpenClawPluginDefinition = {
 id: "exampleai",
 name: "ExampleAI",
 register(api) {
 api.registerProvider({
 id: "exampleai",
 // 鉴权/模型目录/运行时钩子
 });


 api.registerSpeechProvider(
 buildOpenAISpeechProvider({
 id: "exampleai",
 // 厂商语音配置
 }),
 );


 api.registerMediaUnderstandingProvider({
 id: "exampleai",
 capabilities: ["image", "audio", "video"],
 async describeImage(req) {
 return describeImageWithModel({
 provider: "exampleai",
 model: req.model,
 input: req.input,
 });
 },
 async transcribeAudio(req) {
 return transcribeOpenAiCompatibleAudio({
 provider: "exampleai",
 model: req.model,
 input: req.input,
 });
 },
 });


 api.registerWebSearchProvider(
 createPluginBackedWebSearchProvider({
 id: "exampleai-search",
 // 凭证 + 获取逻辑
 }),
 );
 },
};


export default plugin;

重要的不是具体的辅助函数名称,而是这个结构:

  • 一个插件拥有该厂商的所有接口

  • 核心层仍拥有能力契约

  • 通道和功能插件消费 api.runtime.* 辅助工具,而非厂商代码

  • 契约测试可以断言插件注册了它声明拥有的能力

能力示例:视频理解

OpenClaw 已经将图像 / 音频 / 视频理解视为一个共享能力,同样的所有权模型也适用于此:

  1. 核心层定义媒体理解契约

  1. 厂商插件按需注册 describeImagetranscribeAudiodescribeVideo

  1. 通道和功能插件消费共享的核心行为,而非直接对接厂商代码

这可以避免将某个提供商的视频假设硬编码到核心中。插件拥有厂商接口;核心层拥有能力契约和降级行为。 如果 OpenClaw 之后新增了其他领域,比如视频生成,也可以使用同样的流程:先定义核心能力,再让厂商插件注册对应的实现。

契约与校验机制

插件 API 接口是特意做了类型化并集中在 OpenClawPluginApi 中的。该契约定义了支持的注册点,以及插件可以依赖的运行时辅助工具。

为什么这很重要:

  • 插件作者可以获得一个稳定的内部标准

  • 核心层可以拒绝重复的所有权,比如两个插件注册同一个提供商 ID

  • 启动阶段可以为格式错误的注册提供可操作的诊断信息

  • 契约测试可以强制校验内置插件的所有权,避免无声的偏差

有两层校验机制:

  1. 运行时注册校验:插件注册中心会在插件加载时验证注册信息。例如,重复的提供商 ID、重复的语音提供商 ID,以及格式错误的注册,都会生成插件诊断信息,而非未定义的行为。

  1. 契约测试:在测试运行期间,内置插件会被捕获到契约注册中心中,这样 OpenClaw 就可以显式断言所有权。目前该机制用于模型提供商、语音提供商、网页搜索提供商,以及内置注册所有权的校验。

实际效果是,OpenClaw 可以提前知道哪个插件拥有哪个接口。这让核心层和通道可以无缝协作,因为所有权是显式、类型化且可测试的,而非隐式的。

契约的适用范围

好的插件契约应该是:

  • 类型化的

  • 轻量的

  • 能力专属的

  • 由核心层维护

  • 可被多个插件复用

  • 可被通道 / 功能消费,无需了解厂商信息

坏的插件契约是:

  • 隐藏在核心中的厂商专属策略

  • 绕过注册中心的一次性插件逃生舱

  • 通道代码直接侵入厂商实现

  • 不属于 OpenClawPluginApiapi.runtime 的临时运行时对象

如有疑问,请提升抽象层级:先定义能力,再让插件接入它。

执行模型

原生 OpenClaw 插件与网关进程内运行,它们没有沙箱。已加载的原生插件与核心代码拥有相同的进程级信任边界。

这意味着:

  • 原生插件可以注册工具、网络处理器、钩子和服务

  • 原生插件的 bug 可能会导致网关崩溃或不稳定

  • 恶意的原生插件等价于在 OpenClaw 进程内执行任意代码

兼容的包默认更安全,因为 OpenClaw 目前将它们视为元数据 / 内容包,在当前版本中,这主要指内置的技能。

对于非内置插件,请使用白名单和显式的安装 / 加载路径。将工作区插件视为开发时代码,而非生产默认值。

重要的信任提示: plugins.allow 信任的是插件 ID,而非来源。 一个与内置插件拥有相同 ID 的工作区插件,在启用 / 加入白名单后,会显式遮蔽内置的版本。 这是正常的,对本地开发、补丁测试和热修复非常有用。

导出边界

OpenClaw 导出的是能力,而非实现便利性接口。 请保持能力注册为公开的,裁剪非契约的辅助导出:

  • 内置插件专属的辅助子路径

  • 非公共 API 的运行时管道子路径

  • 厂商专属的便利辅助工具

  • 作为实现细节的安装 / 入职辅助工具

加载流水线

启动时,OpenClaw 大致会执行以下步骤:

  1. 发现候选插件根目录

  1. 读取原生或兼容的包清单和包元数据

  1. 拒绝不安全的候选插件

  1. 标准化插件配置(plugins.enabledallowdenyentriesslotsload.paths

  1. 决定每个候选插件的启用状态

  1. 通过 jiti 加载已启用的原生模块

  1. 调用原生的 register(api) 钩子,将注册信息收集到插件注册中心

  1. 将注册中心暴露给命令 / 运行时接口

安全检查会在运行时执行之前完成。如果入口跳出了插件根目录、路径是全局可写的,或是非内置插件的路径所有权看起来可疑,候选插件就会被阻塞。

清单优先的行为

清单是控制面的事实来源。OpenClaw 会用它来:

  • 标识插件

  • 发现声明的通道 / 技能 / 配置 Schema 或包能力

  • 校验 plugins.entries.<id>.config

  • 补充控制 UI 的标签 / 占位符

  • 展示安装 / 目录元数据

对于原生插件,运行时模块是数据面部分,它会注册实际的行为,比如钩子、工具、命令或提供商流程。

加载器缓存内容

OpenClaw 会在进程内维护短期缓存,用于:

  • 发现结果

  • 清单注册数据

  • 已加载的插件注册中心

这些缓存可以减少启动时的突发负载和重复命令的开销。你可以将它们视为短期的性能缓存,而非持久化存储。

性能提示:

  • 设置 OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1 可以禁用这些缓存

  • 可以通过 OPENCLAW_PLUGIN_DISCOVERY_CACHE_MSOPENCLAW_PLUGIN_MANIFEST_CACHE_MS 调整缓存窗口

注册中心模型

已加载的插件不会直接修改任意的核心全局变量,它们会注册到中央插件注册中心。

注册中心会跟踪:

  • 插件记录(身份、来源、原点、状态、诊断信息)

  • 工具

  • 旧版钩子和类型化钩子

  • 通道

  • 提供商

  • 网关 RPC 处理器

  • HTTP 路由

  • CLI 注册器

  • 后台服务

  • 插件专属的命令

核心功能会从该注册中心读取数据,而非直接与插件模块通信。这让加载是单向的: 插件模块 -> 注册中心注册 核心运行时 -> 注册中心消费

这种分离对可维护性非常重要。这意味着大多数核心接口只需要一个集成点:“读取注册中心”,而非 “为每个插件模块做特殊处理”。

会话绑定回调

绑定了会话的插件可以在授权被解决时做出响应。

使用 api.onConversationBindingResolved(...) 来接收绑定请求被批准或拒绝后的回调:



export default {
id: "my-plugin",
register(api) {
api.onConversationBindingResolved(async (event) => {
if (event.status === "approved") {
// 现在已存在该插件 + 会话的绑定
console.log(event.binding?.conversationId);
return;
}
// 请求被拒绝;清理本地的待处理状态
console.log(event.request.conversation.conversationId);
});
},
};

回调 payload 字段:

  • statusapproveddenied

  • decisionallow-onceallow-alwaysdeny

  • binding:已批准请求的已解决绑定

  • request:原始请求摘要、分离提示、发送者 ID,以及会话元数据

该回调仅为通知,它不会改变谁可以绑定会话,并且会在核心授权处理完成后运行。

提供商运行时钩子

提供商插件现在分为两层:

  • 清单元数据:providerAuthEnvVars,用于在运行时加载前快速完成基于环境变量的鉴权查找;以及 providerAuthChoices,用于在运行时加载前快速获取入职 / 鉴权选择标签和 CLI 标志元数据

  • 配置时钩子:目录 / 旧版发现

  • 运行时钩子:resolveDynamicModelprepareDynamicModelnormalizeResolvedModelcapabilitiesprepareExtraParamswrapStreamFnformatApiKeyrefreshOAuthbuildAuthDoctorHintisCacheTtlEligiblebuildMissingAuthMessagesuppressBuiltInModelaugmentModelCatalogisBinaryThinkingsupportsXHighThinkingresolveDefaultThinkingLevelisModernModelRefprepareRuntimeAuthresolveUsageAuthfetchUsageSnapshot

OpenClaw 仍负责通用的代理循环、故障转移、对话记录处理和工具策略。这些钩子是提供商专属行为的扩展接口,无需实现完整的自定义推理传输。

当提供商有基于环境变量的凭证,且通用的鉴权 / 状态 / 模型选择路径需要在不加载插件运行时的情况下感知到它们时,使用清单的 providerAuthEnvVars。当入职 / 鉴权选择的 CLI 界面需要在不加载提供商运行时的情况下,了解提供商的选择 ID、分组标签和简单的单标志鉴权配置时,使用清单的 providerAuthChoices。将提供商运行时的环境变量保留给面向运维的提示,比如入职标签或 OAuth 客户端 ID / 客户端密钥的设置变量。

钩子调用顺序与使用场景

对于模型 / 提供商插件,OpenClaw 会大致按照以下顺序调用钩子。“使用场景” 列是快速决策指南:

# 钩子 作用 使用场景
1 catalog 在 models.json 生成期间,将提供商配置发布到 models.providers 提供商拥有目录或基础 URL 默认值
(内置模型查找) OpenClaw 会优先尝试常规的注册中心 / 目录路径(非插件钩子)
2 resolveDynamicModel 针对本地注册中心中不存在的、提供商专属的模型 ID 的同步 fallback 提供商接受任意上游模型 ID
3 prepareDynamicModel 异步预热,之后重新运行 resolveDynamicModel 提供商需要在解析未知 ID 前获取网络元数据
4 normalizeResolvedModel 在嵌入式运行器使用已解析模型前的最终重写 提供商需要传输重写,但仍使用核心传输
5 capabilities 共享核心逻辑使用的、提供商专属的对话记录 / 工具元数据 提供商需要对话记录 / 提供商系列的特殊处理
6 prepareExtraParams 在通用流选项包装前的请求参数标准化 提供商需要默认请求参数,或按提供商的参数清理
7 wrapStreamFn 通用包装应用后的流包装 提供商需要请求头 / 请求体 / 模型兼容包装,无需自定义传输
8 formatApiKey 鉴权配置格式化:存储的配置变为运行时 apiKey 字符串 提供商存储了额外的鉴权元数据,需要自定义运行时令牌格式
9 refreshOAuth OAuth 刷新覆写,用于自定义刷新端点或刷新失败策略 提供商不兼容共享的 pi-ai 刷新器
10 buildAuthDoctorHint OAuth 刷新失败时附加的修复提示 提供商需要专属的鉴权修复指引,在刷新失败后展示
11 isCacheTtlEligible 代理 / 回传提供商的提示缓存策略 提供商需要代理专属的缓存 TTL 控制
12 buildMissingAuthMessage 通用的缺失鉴权恢复消息的替换内容 提供商需要专属的缺失鉴权恢复提示
13 suppressBuiltInModel 过期上游模型的隐藏,以及可选的用户 facing 错误提示 提供商需要隐藏过期的上游条目,或用厂商提示替换它们
14 augmentModelCatalog 发现后附加的合成 / 最终目录条目 提供商需要在模型列表和选择器中添加合成的向前兼容条目
15 isBinaryThinking 二进制思考提供商的开启 / 关闭推理切换 提供商仅暴露二进制的思考开启 / 关闭
16 supportsXHighThinking 选中模型的 xhigh 推理支持 提供商希望仅在部分模型上开启 xhigh
17 resolveDefaultThinkingLevel 特定模型系列的默认 /think 级别 提供商拥有模型系列的默认 /think 策略
18 isModernModelRef 实时配置文件过滤和冒烟选择的现代模型匹配器 提供商拥有实时 / 冒烟的首选模型匹配
19 prepareRuntimeAuth 在推理前,将配置好的凭证交换为实际的运行时令牌 / 密钥 提供商需要令牌交换,或短期的请求凭证
20 resolveUsageAuth 为 /usage 和相关状态接口解析用量 / 计费凭证 提供商需要自定义的用量 / 配额令牌解析,或是不同的用量凭证
21 fetchUsageSnapshot 鉴权完成后,获取并标准化提供商专属的用量 / 配额快照 提供商需要专属的用量端点或 payload 解析器

如果提供商需要完全自定义的有线协议或自定义请求执行器,那属于另一类扩展。这些钩子适用于仍运行在 OpenClaw 常规推理循环上的提供商行为。

提供商示例



api.registerProvider({
id: "example-proxy",
label: "Example Proxy",
auth: [],
catalog: {
order: "simple",
run: async (ctx) => {
const apiKey = ctx.resolveProviderApiKey("example-proxy").apiKey;
if (!apiKey) {
return null;
}
return {
provider: {
baseUrl: "https://proxy.example.com/v1",
apiKey,
api: "openai-completions",
models: [{ id: "auto", name: "Auto" }],
},
};
},
},
resolveDynamicModel: (ctx) => ({
id: ctx.modelId,
name: ctx.modelId,
provider: "example-proxy",
api: "openai-completions",
baseUrl: "https://proxy.example.com/v1",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 8192,
}),
prepareRuntimeAuth: async (ctx) => {
const exchanged = await exchangeToken(ctx.apiKey);
return {
apiKey: exchanged.token,
baseUrl: exchanged.baseUrl,
expiresAt: exchanged.expiresAt,
};
},
resolveUsageAuth: async (ctx) => {
const auth = await ctx.resolveOAuthToken();
return auth ? { token: auth.token } : null;
},
fetchUsageSnapshot: async (ctx) => {
return await fetchExampleProxyUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn);
},
});

内置插件示例

  • Anthropic 使用了 resolveDynamicModelcapabilitiesbuildAuthDoctorHintresolveUsageAuthfetchUsageSnapshotisCacheTtlEligibleresolveDefaultThinkingLevelisModernModelRef,因为它需要处理 Claude 4.6 的向前兼容、提供商系列提示、鉴权修复指引、用量端点集成、提示缓存适配,以及 Claude 的默认 / 自适应思考策略。

  • OpenAI 使用了 resolveDynamicModelnormalizeResolvedModelcapabilities,以及 buildMissingAuthMessagesuppressBuiltInModelaugmentModelCatalogsupportsXHighThinkingisModernModelRef,因为它需要处理 GPT 5.4 的向前兼容、OpenAI 的 openai-completionsopenai-responses 的标准化、Codex 专属的鉴权提示、Spark 隐藏、合成的 OpenAI 列表条目,以及 GPT-5 的思考 / 实时模型策略。

  • OpenRouter 使用了 catalog,以及 resolveDynamicModelprepareDynamicModel,因为该提供商是透传型的,可能会在 OpenClaw 的静态目录更新前就暴露新的模型 ID;它还使用了 capabilitieswrapStreamFnisCacheTtlEligible,来将提供商专属的请求头、路由元数据、推理补丁和提示缓存策略隔离在核心之外。

  • GitHub Copilot 使用了 catalogauthresolveDynamicModelcapabilities,以及 prepareRuntimeAuthfetchUsageSnapshot,因为它需要厂商专属的设备登录、模型 fallback 行为、Claude 对话记录特殊处理、GitHub 令牌到 Copilot 令牌的交换,以及厂商专属的用量端点。

  • OpenAI Codex 使用了 catalogresolveDynamicModelnormalizeResolvedModelrefreshOAuthaugmentModelCatalog,以及 prepareExtraParamsresolveUsageAuthfetchUsageSnapshot,因为它仍运行在核心的 OpenAI 传输上,但拥有自己的传输 / 基础 URL 标准化、OAuth 刷新 fallback 策略、默认传输选择、合成的 Codex 目录条目,以及 ChatGPT 用量端点集成。

  • Google AI Studio 和 Gemini CLI OAuth 使用了 resolveDynamicModelisModernModelRef,因为它们需要处理 Gemini 3.1 的向前兼容 fallback 和现代模型匹配;Gemini CLI OAuth 还使用了 formatApiKeyresolveUsageAuthfetchUsageSnapshot,用于令牌格式化、令牌解析和配额端点对接。

  • Moonshot 使用了 catalogwrapStreamFn,因为它仍使用共享的 OpenAI 传输,但需要厂商专属的思考 payload 标准化。

  • Kilocode 使用了 catalogcapabilitieswrapStreamFnisCacheTtlEligible,因为它需要厂商专属的请求头、推理 payload 标准化、Gemini 对话记录提示,以及 Anthropic 的缓存 TTL 控制。

  • Z.AI 使用了 resolveDynamicModelprepareExtraParamswrapStreamFnisCacheTtlEligibleisBinaryThinkingisModernModelRefresolveUsageAuthfetchUsageSnapshot,因为它需要处理 GLM-5 fallback、tool_stream 默认值、二进制思考 UX、现代模型匹配,以及用量鉴权和配额获取。

  • Mistral、OpenCode Zen 和 OpenCode Go 仅使用了 capabilities,来将对话记录 / 工具的特殊处理隔离在核心之外。

  • 仅目录的内置提供商,比如 bytepluscloudflare-ai-gatewayhuggingfacekimi-codingmodelstudionvidiaqianfansynthetictogethervenicevercel-ai-gatewayvolcengine,仅使用了 catalog

  • Qwen portal 使用了 catalogauthrefreshOAuth

  • MiniMax 和 Xiaomi 使用了 catalog 加用量钩子,因为它们的 /usage 行为是插件专属的,哪怕推理仍通过共享传输运行。

运行时辅助工具

插件可以通过 api.runtime 访问选定的核心辅助工具。

对于 TTS:



const clip = await api.runtime.tts.textToSpeech({
text: "Hello from OpenClaw",
cfg: api.config,
});
const result = await api.runtime.tts.textToSpeechTelephony({
text: "Hello from OpenClaw",
cfg: api.config,
});
const voices = await api.runtime.tts.listVoices({
provider: "elevenlabs",
cfg: api.config,
});

说明:

  • textToSpeech 返回常规的核心 TTS 输出 payload,适用于文件 / 语音笔记接口

  • 使用核心的 messages.tts 配置和提供商选择

  • 返回 PCM 音频缓冲区 + 采样率。插件需要为提供商重采样 / 编码

  • listVoices 是每个提供商可选的,可用于厂商专属的语音选择器或安装流程

  • 语音列表可以包含更丰富的元数据,比如区域、性别和个性标签,用于提供商感知的选择器

  • 目前 OpenAI 和 ElevenLabs 支持电话场景,Microsoft 暂不支持

  • 插件也可以通过 api.registerSpeechProvider(...) 注册语音提供商



api.registerSpeechProvider({
id: "acme-speech",
label: "Acme Speech",
isConfigured: ({ config }) => Boolean(config.messages?.tts),
synthesize: async (req) => {
return {
audioBuffer: Buffer.from([]),
outputFormat: "mp3",
fileExtension: ".mp3",
voiceCompatible: false,
};
},
});

说明:

  • 将 TTS 策略、降级和回复交付保留在核心层

  • 使用语音提供商来实现厂商专属的合成行为

  • 旧版的 Microsoft edge 输入会被标准化为 microsoft 提供商 ID

  • 首选的所有权模型是面向厂商的:随着 OpenClaw 新增能力契约,一个厂商插件可以同时拥有文本、语音、图像和未来的媒体提供商。

对于图像 / 音频 / 视频理解,插件会注册一个类型化的媒体理解提供商,而非通用的键值集合:



api.registerMediaUnderstandingProvider({
id: "google",
capabilities: ["image", "audio", "video"],
describeImage: async (req) => ({ text: "..." }),
transcribeAudio: async (req) => ({ text: "..." }),
describeVideo: async (req) => ({ text: "..." }),
});

说明:

  • 将编排、降级、配置和通道关联保留在核心层

  • 将厂商行为保留在提供商插件中

  • 增量扩展应保持类型化:新增可选方法、新增可选结果字段、新增可选能力

  • 如果 OpenClaw 之后新增了视频生成等能力,先定义核心能力契约,再让厂商插件基于它注册实现。

对于媒体理解运行时辅助工具,插件可以调用:



const image = await api.runtime.mediaUnderstanding.describeImageFile({
filePath: "/tmp/inbound-photo.jpg",
cfg: api.config,
agentDir: "/tmp/agent",
});
const video = await api.runtime.mediaUnderstanding.describeVideoFile({
filePath: "/tmp/inbound-video.mp4",
cfg: api.config,
});

对于音频转录,插件可以使用媒体理解运行时,或是旧的 STT 别名:



const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
filePath: "/tmp/inbound-audio.ogg",
cfg: api.config,
// 当无法可靠推断 MIME 时可选:
mime: "audio/ogg",
});

说明:

  • api.runtime.mediaUnderstanding.* 是图像 / 音频 / 视频理解的首选共享接口

  • 使用核心的媒体理解音频配置(tools.media.audio)和提供商降级顺序

  • 当没有转录输出时(例如跳过 / 不支持的输入),返回 { text: undefined }

  • api.runtime.stt.transcribeAudioFile(...) 仍作为兼容别名存在。

插件也可以通过 api.runtime.subagent 启动后台子代理运行:



const result = await api.runtime.subagent.run({
sessionKey: "agent:main:subagent:search-helper",
message: "Expand this query into focused follow-up searches.",
provider: "openai",
model: "gpt-4.1-mini",
deliver: false,
});

说明:

  • providermodel 是每次运行的可选覆写,不是持久的会话变更

  • OpenClaw 仅对可信调用者生效这些覆写字段

  • 对于插件专属的 fallback 运行,运维人员必须通过 plugins.entries.<id>.subagent.allowModelOverride: true 来开启

  • 使用 plugins.entries.<id>.subagent.allowedModels 来限制可信插件只能使用特定的标准提供商 / 模型目标,或使用 "*" 来显式允许任意目标

  • 不可信的插件子代理运行仍可工作,但覆写请求会被拒绝,而非静默 fallback。

对于网页搜索,插件可以消费共享的运行时辅助工具,而非侵入代理工具的关联逻辑:



const providers = api.runtime.webSearch.listProviders({
config: api.config,
});
const result = await api.runtime.webSearch.search({
config: api.config,
args: {
query: "OpenClaw plugin runtime helpers",
count: 5,
},
});

插件也可以通过 api.registerWebSearchProvider(...) 注册网页搜索提供商。

说明:

  • 将提供商选择、凭证解析和共享请求语义保留在核心层

  • 使用网页搜索提供商来实现厂商专属的搜索传输

  • api.runtime.webSearch.* 是功能 / 通道插件的首选共享接口,让它们可以在不依赖代理工具包装的情况下使用搜索行为。

网关 HTTP 路由

插件可以通过 api.registerHttpRoute(...) 暴露 HTTP 端点:



api.registerHttpRoute({
path: "/acme/webhook",
auth: "plugin",
match: "exact",
handler: async (_req, res) => {
res.statusCode = 200;
res.end("ok");
return true;
},
});

路由字段:

  • path:网关 HTTP 服务器下的路由路径

  • auth:必填。使用 "gateway" 来要求常规的网关鉴权,或使用 "plugin" 来使用插件管理的鉴权 /webhook 验证

  • match:可选。"exact"(默认)或 "prefix"

  • replaceExisting:可选。允许同一个插件替换自己已有的路由注册

  • handler:当路由处理了请求时返回 true

说明:

  • api.registerHttpHandler(...) 已废弃,请使用 api.registerHttpRoute(...)

  • 插件路由必须显式声明 auth

  • 除非设置了 replaceExisting: true,否则精确路径 + 匹配的冲突会被拒绝,且一个插件不能替换另一个插件的路由

  • 不同鉴权级别的重叠路由会被拒绝。请仅在同一个鉴权级别维护精确 / 前缀的 fallback 链。

插件 SDK 导入路径

在开发插件时,请使用 SDK 子路径,而非整体的 openclaw/plugin-sdk 导入:

  • openclaw/plugin-sdk/plugin-entry:用于插件注册原语

  • openclaw/plugin-sdk/core:用于通用的共享插件面向契约

  • 稳定的通道原语,比如 openclaw/plugin-sdk/channel-setupopenclaw/plugin-sdk/channel-pairingopenclaw/plugin-sdk/channel-reply-pipelineopenclaw/plugin-sdk/secret-inputopenclaw/plugin-sdk/webhook-ingress,用于共享的安装 / 鉴权 / 回复 /webhook 关联

  • 领域子路径,比如 openclaw/plugin-sdk/channel-config-helpersopenclaw/plugin-sdk/channel-config-schemaopenclaw/plugin-sdk/channel-policyopenclaw/plugin-sdk/channel-runtimeopenclaw/plugin-sdk/config-runtimeopenclaw/plugin-sdk/agent-runtimeopenclaw/plugin-sdk/lazy-runtimeopenclaw/plugin-sdk/reply-historyopenclaw/plugin-sdk/routingopenclaw/plugin-sdk/runtime-storeopenclaw/plugin-sdk/directory-runtime,用于共享的运行时 / 配置辅助工具

  • 窄通道核心子路径,比如 openclaw/plugin-sdk/discord-coreopenclaw/plugin-sdk/telegram-coreopenclaw/plugin-sdk/whatsapp-core,用于通道专属的原语,这些原语应比完整的通道辅助导出文件更轻量

内置扩展的内部逻辑保持私有。外部插件应仅使用 openclaw/plugin-sdk/* 子路径。OpenClaw 核心 / 测试代码可以使用仓库的公共入口,比如 extensions/<id>/index.jsapi.jsruntime-api.jssetup-entry.js,以及窄范围的文件,比如 login-qr-api.js。永远不要从核心或其他扩展导入 extensions/<id>/src/*

仓库入口拆分:

  • extensions/<id>/api.js 是辅助 / 类型导出文件

  • extensions/<id>/runtime-api.js 是仅运行时的导出文件

  • extensions/<id>/index.js 是内置插件入口

  • extensions/<id>/setup-entry.js 是安装插件入口

  • openclaw/plugin-sdk/telegram:用于 Telegram 通道插件类型和共享的通道面向辅助工具。内置的 Telegram 实现内部逻辑保持为内置扩展私有。

  • openclaw/plugin-sdk/discord:用于 Discord 通道插件类型和共享的通道面向辅助工具。内置的 Discord 实现内部逻辑保持为内置扩展私有。

  • openclaw/plugin-sdk/slack:用于 Slack 通道插件类型和共享的通道面向辅助工具。内置的 Slack 实现内部逻辑保持为内置扩展私有。

  • openclaw/plugin-sdk/imessage:用于 iMessage 通道插件类型和共享的通道面向辅助工具。内置的 iMessage 实现内部逻辑保持为内置扩展私有。

  • openclaw/plugin-sdk/whatsapp:用于 WhatsApp 通道插件类型和共享的通道面向辅助工具。内置的 WhatsApp 实现内部逻辑保持为内置扩展私有。

  • openclaw/plugin-sdk/bluebubbles 保持公开,因为它提供了一个小而专注的辅助接口,是特意共享的。

兼容性提示:

  • 新代码请避免使用根路径的 openclaw/plugin-sdk 导出

  • 优先使用窄的稳定原语。新的 setup/pairing/reply/secret-input/webhook 子路径是新的内置和外部插件开发的目标契约。

  • 内置扩展专属的辅助导出文件默认不是稳定的。如果一个辅助工具仅被内置扩展需要,请将它保留在扩展的本地 api.jsruntime-api.js 之后,而非提升到 openclaw/plugin-sdk/<extension> 中。

  • 能力专属的子路径,比如 image-generationmedia-understandingspeech 存在是因为内置 / 原生插件目前在使用它们。它们的存在本身不代表每个导出的辅助工具都是长期稳定的外部契约。

消息工具 Schema

插件应负责通道专属的 describeMessageTool(...) Schema 贡献。将提供商专属的字段保留在插件中,而非共享的核心中。

对于共享的可移植 Schema 片段,请复用通过 openclaw/plugin-sdk/channel-runtime 导出的通用辅助工具:

  • createMessageToolButtonsSchema():用于按钮网格风格的 payload

  • createMessageToolCardSchema():用于结构化卡片 payload

如果一个 Schema 形状仅对某个提供商有意义,请在该插件的源码中定义它,而非提升到共享的 SDK 中。

通道目标解析

通道插件应负责通道专属的目标语义。将共享的出站主机保持通用,使用消息适配器接口来处理厂商规则:

  • messaging.inferTargetChatType({ to }):在目录查找前,决定一个标准化的目标应被视为直接、群组还是通道。

  • messaging.targetResolver.looksLikeId(raw, normalized):告诉核心,一个输入是否应直接跳过 ID 式解析,而非目录搜索。

  • messaging.targetResolver.resolveTarget(...):当核心在标准化后,或目录未命中后,需要最终的厂商专属解析时的插件 fallback。

  • messaging.resolveOutboundSessionRoute(...):在目标解析完成后,负责厂商专属的会话路由构建。

推荐的拆分:

  • 使用 inferTargetChatType 来做分类决策,这些决策应在搜索 peers / 群组之前完成。

  • 使用 looksLikeId 来做 “将此视为显式 / 原生目标 ID” 的检查。

  • 使用 resolveTarget 来做厂商专属的标准化 fallback,而非大范围的目录搜索。

  • 将厂商原生 ID,比如聊天 ID、线程 ID、JID、句柄和房间 ID,保留在目标值或厂商专属的参数中,而非通用的 SDK 字段中。

配置驱动的目录

从配置中派生目录条目的插件,应将该逻辑保留在插件中,并复用 openclaw/plugin-sdk/directory-runtime 中的共享辅助工具。

当通道需要配置驱动的 peers / 群组时使用,比如:

  • 白名单驱动的 DM peers

  • 配置的通道 / 群组映射

  • 账户范围的静态目录 fallback

directory-runtime 中的共享辅助工具仅处理通用操作:

  • 查询过滤

  • 限制应用

  • 去重 / 标准化辅助工具

  • 构建 ChannelDirectoryEntry[]

通道专属的账户检查和 ID 标准化应保留在插件实现中。

提供商目录

提供商插件可以通过 registerProvider({ catalog: { run(...) { ... } } }) 为推理定义模型目录。

catalog.run(...) 返回的结构与 OpenClaw 写入 models.providers 的结构相同:

  • { provider } 用于单个提供商条目

  • { providers } 用于多个提供商条目

当插件拥有提供商专属的模型 ID、基础 URL 默认值,或鉴权 gated 的模型元数据时,使用 catalog

catalog.order 控制插件的目录相对于 OpenClaw 内置的隐式提供商的合并时机:

  • simple:普通的 API 密钥或环境驱动的提供商

  • profile:当鉴权配置存在时才出现的提供商

  • paired:会合成多个相关提供商条目的提供商

  • late:最后一轮,在其他隐式提供商之后

在键冲突时,后面的提供商优先级更高,所以插件可以显式覆写拥有相同提供商 ID 的内置提供商条目。

兼容性:

  • discovery 仍作为旧版别名工作

  • 如果同时注册了 catalogdiscovery,OpenClaw 会使用 catalog

只读通道检查

如果你的插件注册了通道,建议在实现 resolveAccount(...) 的同时,实现 plugin.config.inspectAccount(cfg, accountId)

为什么:

  • resolveAccount(...) 是运行时路径,它可以假设凭证已完全物化,并且在所需密钥缺失时可以快速失败。

  • 只读命令路径,比如 openclaw statusopenclaw status --allopenclaw channels statusopenclaw channels resolve,以及诊断 / 配置修复流程,不应仅为了描述配置就物化运行时凭证。

推荐的 inspectAccount(...) 行为:

  • 仅返回描述性的账户状态

  • 保留启用和已配置状态

  • 在相关时包含凭证来源 / 状态字段,比如:

  • tokenSourcetokenStatus

  • botTokenSourcebotTokenStatus

  • appTokenSourceappTokenStatus

  • signingSecretSourcesigningSecretStatus

  • 你无需仅为了报告只读可用性就返回原始的令牌值。返回 tokenStatus: "available"(以及对应的来源字段)就足够用于状态类命令了。

  • 当凭证通过 SecretRef 配置,但在当前命令路径中不可用时,使用 configured_unavailable

这让只读命令可以报告 “已配置,但在当前命令路径中不可用”,而非崩溃,或是错误地将账户报告为未配置。

插件包

一个插件目录可以包含一个带有 openclaw.extensionspackage.json



{
"name": "my-pack",
"openclaw": {
"extensions": ["./src/safety.ts", "./src/tools.ts"],
"setupEntry": "./src/setup-entry.ts"
}
}

每个条目都会成为一个插件。如果包列出了多个扩展,插件 ID 会变为 name/<fileBase>

如果你的插件导入了 npm 依赖,请在该目录中安装它们,这样 node_modules 就可用了(执行 npm installpnpm install)。

安全防护:每个 openclaw.extensions 条目在符号链接解析后,必须保留在插件目录内部。跳出包目录的条目会被拒绝。

安全提示:openclaw plugins install 会使用 npm install --ignore-scripts 安装插件依赖(不执行生命周期脚本)。请保持插件依赖树为 “纯 JS/TS”,避免需要 postinstall 构建的包。

可选:openclaw.setupEntry 可以指向一个轻量的仅安装模块。当 OpenClaw 需要为禁用的通道插件提供安装界面,或是通道插件已启用但仍未配置时,它会加载 setupEntry,而非完整的插件入口。这可以让启动和安装更轻量,当你的主插件入口还关联了工具、钩子或其他仅运行时代码时。

可选:openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen 可以让通道插件在网关的监听前启动阶段,也使用相同的 setupEntry 路径,哪怕通道已经配置完成。

仅当 setupEntry 完全覆盖了启动前必须存在的启动接口时,才开启该标志。实际上,这意味着安装入口必须注册启动依赖的所有通道专属能力,比如:

  • 通道注册本身

  • 任何在网关开始监听前必须可用的 HTTP 路由

  • 任何在同一窗口必须存在的网关方法、工具或服务

如果你的完整入口仍拥有任何所需的启动能力,请勿开启该标志。保持插件的默认行为,让 OpenClaw 在启动时加载完整入口。

示例:



{
"name": "@scope/my-channel",
"openclaw": {
"extensions": ["./index.ts"],
"setupEntry": "./setup-entry.ts",
"startup": {
"deferConfiguredChannelFullLoadUntilAfterListen": true
}
}
}

通道目录元数据

通道插件可以通过 openclaw.channel 来宣传安装 / 发现元数据,通过 openclaw.install 来提供安装提示。这让核心目录可以保持无数据。

示例:



{
"name": "@openclaw/nextcloud-talk",
"openclaw": {
"extensions": ["./index.ts"],
"channel": {
"id": "nextcloud-talk",
"label": "Nextcloud Talk",
"selectionLabel": "Nextcloud Talk (自托管)",
"docsPath": "/channels/nextcloud-talk",
"docsLabel": "nextcloud-talk",
"blurb": "通过 Nextcloud Talk webhook 机器人实现自托管聊天。",
"order": 65,
"aliases": ["nc-talk", "nc"]
},
"install": {
"npmSpec": "@openclaw/nextcloud-talk",
"localPath": "extensions/nextcloud-talk",
"defaultChoice": "npm"
}
}
}

OpenClaw 也可以合并外部的通道目录(例如 MPM 注册表导出)。将 JSON 文件放在以下任意位置:

  • ~/.openclaw/mpm/plugins.json

  • ~/.openclaw/mpm/catalog.json

  • ~/.openclaw/plugins/catalog.json

或者将 OPENCLAW_PLUGIN_CATALOG_PATHS(或 OPENCLAW_MPM_CATALOG_PATHS)指向一个或多个 JSON 文件(用逗号 / 分号 / PATH 分隔)。每个文件应包含 { "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] }

上下文引擎插件

上下文引擎插件负责会话上下文的编排,包括摄入、组装和压缩。你可以通过插件的 api.registerContextEngine(id, factory) 注册它们,然后通过 plugins.slots.contextEngine 选择激活的引擎。

当你的插件需要替换或扩展默认的上下文流水线,而非仅添加内存搜索或钩子时,使用它。



export default function (api) {
api.registerContextEngine("lossless-claw", () => ({
info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
async ingest() {
return { ingested: true };
},
async assemble({ messages }) {
return { messages, estimatedTokens: 0 };
},
async compact() {
return { ok: true, compacted: false };
},
}));
}

如果你的引擎不拥有压缩算法,请保持 compact() 实现,并显式委托:



import { delegateCompactionToRuntime } from "openclaw/plugin-sdk/core";
export default function (api) {
api.registerContextEngine("my-memory-engine", () => ({
info: {
id: "my-memory-engine",
name: "My Memory Engine",
ownsCompaction: false,
},
async ingest() {
return { ingested: true };
},
async assemble({ messages }) {
return { messages, estimatedTokens: 0 };
},
async compact(params) {
return await delegateCompactionToRuntime(params);
},
}));
}

新增能力

当插件需要的行为不符合当前的 API 时,不要通过私有侵入绕过插件系统,而是新增缺失的能力。

推荐的流程:

  1. 定义核心契约:决定核心应拥有哪些共享行为:策略、降级、配置合并、生命周期、通道面向语义,以及运行时辅助工具的结构。

  1. 添加类型化的插件注册 / 运行时接口:扩展 OpenClawPluginApi 和 / 或 api.runtime,添加最小可用的类型化能力接口。

  1. 关联核心 + 通道 / 功能消费者:通道和功能插件应通过核心消费新能力,而非直接导入厂商实现。

  1. 注册厂商实现:然后厂商插件就可以基于该能力注册它们的后端。

  1. 添加契约覆盖:添加测试,让所有权和注册结构随时间保持显式。

这就是 OpenClaw 如何保持 opinionated,同时不会硬编码为某个提供商的世界观的方式。如需具体的文件检查清单和完整示例,请查看能力手册

能力检查清单

当你新增一个能力时,实现通常需要同时覆盖这些接口:

  • 核心契约类型,放在 src/<capability>/types.ts

  • 核心运行器 / 运行时辅助工具,放在 src/<capability>/runtime.ts

  • 插件 API 注册接口,放在 src/plugins/types.ts

  • 插件注册中心关联,放在 src/plugins/registry.ts

  • 插件运行时暴露,当功能 / 通道插件需要消费时,放在 src/plugins/runtime/*

  • 捕获 / 测试辅助工具,放在 src/test-utils/plugin-registration.ts

  • 所有权 / 契约断言,放在 src/plugins/contracts/registry.ts

  • 运维 / 插件文档,放在 docs/

如果其中某个接口缺失,通常意味着该能力还没有完全集成。

能力模板

最小化模式:



// 核心契约
export type VideoGenerationProviderPlugin = {
id: string;
label: string;
generateVideo: (req: VideoGenerationRequest) => Promise<VideoGenerationResult>;
};


// 插件 API
api.registerVideoGenerationProvider({
id: "openai",
label: "OpenAI",
async generateVideo(req) {
return await generateOpenAiVideo(req);
},
});


// 供功能/通道插件使用的共享运行时辅助工具
const clip = await api.runtime.videoGeneration.generateFile({
prompt: "Show the robot walking through the lab.",
cfg,
});

契约测试模式:



expect(findVideoGenerationProviderIdsForPlugin("openai")).toEqual(["openai"]);

这保持了规则的简单:

  • 核心层拥有能力契约 + 编排

  • 厂商插件拥有厂商实现

  • 功能 / 通道插件消费运行时辅助工具

  • 契约测试保持所有权显式
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号