Appearance
要理解插件加载失败或路由注册冲突,核心是掌握 OpenClaw 的加载管道和注册表模型。插件在启动时经历发现、安全过滤、配置归一化、加载与注册等步骤,注册表集中管理工具、渠道、提供者、钩子等。本文列出了 40+ provider 运行时钩子的调用顺序和适用场景,以及 HTTP 路由注册时必须声明 auth 且不能跨插件覆盖等限制。
OpenClaw 插件架构内部:加载管道、注册表与运行时钩子参考
关于公开的能力模型、插件形状和所有权/执行契约,请参见 插件架构。本页是内部机制的参考文档:加载管道、注册表、运行时钩子、Gateway HTTP 路由、导入路径和 schema 表格。
加载管道
启动时,OpenClaw 大致执行以下步骤:
- 发现候选插件根目录
- 读取原生或兼容 bundle 的清单和包元数据
- 拒绝不安全的候选
- 归一化插件配置(
plugins.enabled、allow、deny、entries、slots、load.paths) - 决定每个候选的启用状态
- 加载已启用的原生模块:内置 bundle 模块使用原生加载器;第三方本地源 TypeScript 使用紧急 Jiti 回退
- 调用原生
register(api)钩子并将注册项收集到插件注册表 - 将注册表暴露给命令/运行时表面
INFO
activate 是 register 的遗留别名——加载器解析存在的那个(def.register ?? def.activate)并在同一时刻调用它。所有内置插件都使用 register;新插件应优先使用 register。
安全门控发生在运行时执行之前。当入口转义了插件根目录、路径是全局可写的,或者非 bundle 插件路径所有权可疑时,候选会被阻止。
被阻止的候选仍会与它们的插件 id 关联以用于诊断。如果配置仍引用该 id,验证会报告该插件存在但被阻止,并指向路径安全警告,而不是将配置项视为过时。
清单优先行为
清单是控制平面的事实来源。OpenClaw 使用它来:
- 识别插件
- 发现声明的渠道/技能/配置 schema 或 bundle 能力
- 验证
plugins.entries.<id>.config - 增强控制界面标签/占位符
- 显示安装/目录元数据
- 保存轻量激活和设置描述,无需加载插件运行时
对于原生插件,运行时模块是数据平面部分。它注册实际行为,如钩子、工具、命令或提供者流程。
可选的清单 activation 和 setup 块保留在控制平面。它们是仅元数据的描述,用于激活规划和设置发现;它们不会替代运行时注册、register(...) 或 setupEntry。第一个实时激活消费者现在使用清单命令、渠道和提供者提示,在更广泛的注册表具体化之前缩小插件加载范围:
- CLI 加载缩小到拥有请求的主要命令的插件
- 渠道设置/插件解析缩小到拥有请求的渠道 id 的插件
- 显式提供者设置/运行时解析缩小到拥有请求的提供者 id 的插件
- Gateway 启动规划使用
activation.onStartup进行显式启动导入和启动选择退出;没有启动元数据的插件仅通过更窄的激活触发器加载
请求时运行时预加载如果要求宽泛的 all 范围,仍会从配置、启动规划、配置的渠道、slots 和自动启用规则中派生出显式的有效插件 id 集。如果该派生集为空,OpenClaw 会加载一个空的运行时注册表,而不是扩展每个可发现的插件。
激活规划器同时暴露一个仅 id 的 API(用于现有调用者)和一个规划 API(用于新诊断)。规划条目报告选择插件的原因,将显式的 activation.* 规划提示与清单所有权回退(如 providers、channels、commandAliases、setup.providers、contracts.tools 和钩子)分开。该原因分割是兼容性边界:现有插件元数据继续工作,而新代码可以在不更改运行时加载语义的情况下检测宽泛提示或回退行为。
设置发现现在优先使用描述符拥有的 id(如 setup.providers 和 setup.cliBackends)来缩小候选插件,然后才回退到 setup-api 用于仍然需要设置时运行时钩子的插件。提供者设置列表使用清单 providerAuthChoices、描述符派生的设置选项和安装目录元数据,无需加载提供者运行时。显式的 setup.requiresRuntime: false 是仅描述符的截止条件;省略 requiresRuntime 会保留遗留的 setupp-api 回退以保持兼容性。如果多个发现的插件声明相同的归一化设置提供者或 CLI 后端 id,设置查找会拒绝模糊的所有者,而不是依赖发现顺序。当设置运行时确实执行时,注册表诊断会报告 setup.providers / setup.cliBackends 与 setup-api 注册的提供者或 CLI 后端之间的漂移,而不会阻止旧插件。
插件缓存边界
OpenClaw 不会在墙钟时间窗口内缓存插件发现结果或直接清单注册表数据。安装、清单编辑和加载路径更改必须在下次显式元数据读取或快照重建时可见。清单文件解析器可能会保持一个有界文件签名缓存,以打开的清单路径、inode、大小和时间戳为键;该缓存仅避免重新解析未更改的字节,不得缓存发现、注册表、所有者或策略答案。
安全的元数据快速路径是显式对象所有权,而不是隐藏缓存。Gateway 启动热路径应将当前的 PluginMetadataSnapshot、派生的 PluginLookUpTable 或显式清单注册表通过调用链传递。配置验证、启动自动启用、插件引导和提供者选择可以在这些对象代表当前配置和插件库存时重用它们。设置查找仍然按需重建清单元数据,除非特定的设置路径接收到显式清单注册表;将其保留为冷路径回退,而不是添加隐藏的查找缓存。当输入更改时,重建并替换快照,而不是突变它或保留历史副本。对活动插件注册表和捆绑渠道引导辅助程序的视图应从当前注册表/根重新计算。短期映射在一次调用内用于去重工作或防止重入是可以的;它们不得成为进程元数据缓存。
对于插件加载,持久缓存层是运行时加载。它可以在代码或安装的工件实际加载时重用加载器状态,例如:
PluginLoaderCacheState和兼容的活动运行时注册表- jiti/模块缓存和公共表面加载器缓存(用于避免重复导入相同的运行时表面)
- 安装的插件工件的文件系统缓存
- 用于路径规范化或重复解析的短期每调用映射
这些缓存是数据平面实现细节。它们不得回答控制平面问题,例如“哪个插件拥有这个提供者?”,除非调用者明确要求运行时加载。
不要为以下内容添加持久或墙钟缓存:
- 发现结果
- 直接清单注册表
- 从已安装插件索引重建的清单注册表
- 提供者所有者查找、模型抑制、提供者策略或公共工件元数据
- 任何其他清单派生的答案,其中更改的清单、已安装索引或加载路径应在下次元数据读取时可见
从持久化已安装插件索引重建清单元数据的调用者按需重建注册表。已安装索引是耐用的源平面状态;它不是隐藏的进程内元数据缓存。
注册表模型
已加载的插件不会直接突变随机的核心全局变量。它们注册到中央插件注册表中。
注册表跟踪:
- 插件记录(标识、源、来源、状态、诊断)
- 工具
- 遗留钩子和类型化钩子
- 渠道
- 提供者
- Gateway RPC 处理器
- HTTP 路由
- CLI 注册器
- 后台服务
- 插件拥有的命令
核心功能然后从该注册表读取,而不是直接与插件模块交互。这使加载保持单向:
- 插件模块 -> 注册表注册
- 核心运行时 -> 注册表消费
这种分离对可维护性很重要。这意味着大多数核心表面只需要一个集成点:“读取注册表”,而不是“特殊情况每个插件模块”。
会话绑定回调
绑定会话的插件可以在批准被解决时做出反应。
使用 api.onConversationBindingResolved(...) 在绑定请求被批准或拒绝后接收回调:
ts
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);
});
},
};回调载荷字段:
status:"approved"或"denied"decision:"allow-once"、"allow-always"或"deny"binding: 已批准的请求的已解决绑定request: 原始请求摘要、分离提示、发送者 id 和会话元数据
此回调仅为通知。它不会更改谁被允许绑定会话,并且在核心批准处理完成后运行。
提供者运行时钩子
提供者插件有三个层次:
- 清单元数据用于便宜的预运行时查找:
setup.providers[].envVars、已弃用的兼容性providerAuthEnvVars、providerAuthAliases、providerAuthChoices和channelEnvVars。 - 配置时钩子:
catalog(遗留discovery)加上applyConfigDefaults。 - 运行时钩子:40 多个可选钩子,涵盖认证、模型解析、流包装、思考级别、重播策略和使用端点。参见完整列表钩子顺序与使用。
OpenClaw 仍然拥有通用的智能体循环、故障转移、转录处理和工具策略。这些钩子是提供者特定行为的扩展表面,无需完全自定义推理传输。
当提供者有基于 env 的凭据,通用认证/状态/模型选择器路径应在不加载插件运行时的情况下看到时,使用清单 setup.providers[].envVars。在弃用窗口期间,providerAuthEnvVars 仍会被兼容性适配器读取,使用它的非 bundle 插件会收到清单诊断。当一个提供者 id 应重用另一个提供者 id 的 env 变量、认证配置文件、配置支持的认证和 API-key 入门选项时,使用清单 providerAuthAliases。当入门/认证选择 CLI 表面应知道提供者的选择 id、组标签和简单一键认证连线而无需加载提供者运行时,使用清单 providerAuthChoices。将提供者运行时 envVars 用于操作员面向的提示,如入门标签或 OAuth client-id/client-secret 设置变量。
当渠道有 env 驱动的认证或设置,通用 shell-env 回退、配置/状态检查或设置提示应在不加载渠道运行时的情况下看到时,使用清单 channelEnvVars。
钩子顺序与使用
对于模型/提供者插件,OpenClaw 按大致顺序调用钩子。“何时使用”列是快速决策指南。 OpenClaw 不再调用的兼容性仅提供者字段(如 ProviderPlugin.capabilities 和 suppressBuiltInModel)有意未列出。
| # | 钩子 | 作用 | 何时使用 |
|---|---|---|---|
| 1 | catalog | 在 models.json 生成期间将提供者配置发布到 models.providers | 提供者拥有目录或 baseUrl 默认值 |
| 2 | applyConfigDefaults | 在配置具体化期间应用提供者拥有的全局配置默认值 | 默认值取决于认证模式、env 或提供者模型族语义 |
| -- | (内置模型查找) | OpenClaw 首先尝试正常注册表/目录路径 | (非插件钩子) |
| 3 | normalizeModelId | 在查找之前规范化遗留或预览模型 id 别名 | 提供者拥有在规范模型解析之前的别名清理 |
| 4 | normalizeTransport | 在通用模型组装之前规范化提供者族 api / baseUrl | 提供者拥有同一传输族中自定义提供者 id 的传输清理 |
| 5 | normalizeConfig | 在运行时/提供者解析之前规范化 models.providers.<id> | 提供者需要应随插件一起存在的配置清理;捆绑的 Google 族助手也回退支持支持的 Google 配置条目 |
| 6 | applyNativeStreamingUsageCompat | 对配置提供者应用原生流式用法兼容重写 | 提供者需要端点驱动的原生流式用法元数据修复 |
| 7 | resolveConfigApiKey | 在运行时认证加载之前为配置提供者解析 env 标记认证 | 提供者有提供者拥有的 env 标记 API-key 解析;amazon-bedrock 也有内置的 AWS env 标记解析器 |
| 8 | resolveSyntheticAuth | 在不持久化明文的情况下展示本地/自托管或配置支持的认证 | 提供者可以使用合成/本地凭据标记操作 |
| 9 | resolveExternalAuthProfiles | 覆盖提供者拥有的外部认证配置文件;默认 persistence 是 runtime-only 用于 CLI/应用拥有的凭据 | 提供者重用外部认证凭据而不持久化复制的刷新令牌;在清单中声明 contracts.externalAuthProviders |
| 10 | shouldDeferSyntheticProfileAuth | 将存储的合成配置文件占位符降低到 env/配置支持的认证之后 | 提供者存储不应优先的合成占位符配置文件 |
| 11 | resolveDynamicModel | 同步回退用于本地注册表中尚未出现的提供者拥有的模型 id | 提供者接受任意上游模型 id |
| 12 | prepareDynamicModel | 异步预热,然后 resolveDynamicModel 再次运行 | 提供者在解析未知 id 之前需要网络元数据 |
| 13 | normalizeResolvedModel | 在嵌入式运行器使用已解析模型之前的最终重写 | 提供者需要传输重写但仍然使用核心传输 |
| 14 | contributeResolvedModelCompat | 为另一个兼容传输后面的供应商模型贡献兼容标志 | 提供者识别自己模型在代理传输上,而不接管提供者 |
| 15 | normalizeToolSchemas | 在嵌入式运行器看到工具 schema 之前规范化它们 | 提供者需要传输族 schema 清理 |
| 16 | inspectToolSchemas | 在规范化之后展示提供者拥有的 schema 诊断 | 提供者想要关键字警告而不教核心提供者特定规则 |
| 17 | resolveReasoningOutputMode | 选择原生与标记推理输出契约 | 提供者需要标记的推理/最终输出而不是原生字段 |
| 18 | prepareExtraParams | 在通用流选项包装器之前的请求参数规范化 | 提供者需要默认请求参数或每个提供者的参数清理 |
| 19 | createStreamFn | 完全替换正常流路径为自定义传输 | 提供者需要自定义连线协议,而不仅仅是包装器 |
| 20 | wrapStreamFn | 在通用包装器应用之后的流包装器 | 提供者需要请求头/体/模型兼容包装器,无需自定义传输 |
| 21 | resolveTransportTurnState | 附加原生每轮传输头或元数据 | 提供者希望通用传输发送提供者原生轮标识 |
| 22 | resolveWebSocketSessionPolicy | 附加原生 WebSocket 头或会话冷却策略 | 提供者希望通用 WS 传输调整会话头或回退策略 |
| 23 | formatApiKey | 认证配置文件格式化器:存储的配置文件成为运行时 apiKey 字符串 | 提供者存储额外的认证元数据并需要自定义运行时令牌形状 |
| 24 | refreshOAuth | OAuth 刷新覆盖用于自定义刷新端点或刷新失败策略 | 提供者不适合共享的 pi-ai 刷新器 |
| 25 | buildAuthDoctorHint | 当 OAuth 刷新失败时附加的修复提示 | 提供者需要在刷新失败后提供者拥有的认证修复指导 |
| 26 | matchesContextOverflowError | 提供者拥有的上下文窗口溢出匹配器 | 提供者有通用启发式方法会遗漏的原始溢出错误 |
| 27 | classifyFailoverReason | 提供者拥有的故障转移原因分类 | 提供者可以将原始 API/传输错误映射到 rate-limit/overload 等 |
| 28 | isCacheTtlEligible | 提示缓存策略用于代理/回传提供者 | 提供者需要代理特定的缓存 TTL 门控 |
| 29 | buildMissingAuthMessage | 替换通用丢失认证恢复消息 | 提供者需要提供者特定的丢失认证恢复提示 |
| 30 | augmentModelCatalog | 在发现之后附加的合成/最终目录行 | 提供者需要在 models list 和选择器中合成前向兼容行 |
| 31 | resolveThinkingProfile | 模型特定的 /think 级别设置、显示标签和默认值 | 提供者为选定模型暴露自定义思考阶梯或二元标签 |
| 32 | isBinaryThinking | 开/关推理切换兼容性钩子 | 提供者仅暴露二元思考开/关 |
| 33 | supportsXHighThinking | xhigh 推理支持兼容性钩子 | 提供者只希望在模型子集上使用 xhigh |
| 34 | resolveDefaultThinkingLevel | 默认 /think 级别兼容性钩子 | 提供者为模型族拥有默认 /think 策略 |
| 35 | isModernModelRef | 现代模型匹配器用于实时配置文件和烟雾测试选择 | 提供者拥有实时/烟雾首选模型匹配 |
| 36 | prepareRuntimeAuth | 在推理之前将配置的凭据交换为实际运行时令牌/密钥 | 提供者需要令牌交换或短期请求凭据 |
| 37 | resolveUsageAuth | 为 /usage 和相关状态表面解析使用/计费凭据 | 提供者需要自定义使用/配额令牌解析或不同的使用凭据 |
| 38 | fetchUsageSnapshot | 在认证解析后获取并规范化提供者特定的使用/配额快照 | 提供者需要提供者特定的使用端点或载荷解析器 |
| 39 | createEmbeddingProvider | 为记忆/搜索构建提供者拥有的嵌入适配器 | 记忆嵌入行为属于提供者插件 |
| 40 | buildReplayPolicy | 返回控制提供者的转录处理的重播策略 | 提供者需要自定义转录策略(例如思考块剥离) |
| 41 | sanitizeReplayHistory | 在通用转录清理后重写重播历史 | 提供者需要提供者特定的重播重写,超出共享压缩助手 |
| 42 | validateReplayTurns | 在嵌入式运行器之前的最终重播轮验证或重塑 | 提供者传输需要在通用消毒后更严格的轮验证 |
| 43 | onModelSelected | 运行提供者拥有的后选择副作用 | 提供者需要在模型变为活动时进行遥测或提供者拥有的状态 |
normalizeModelId、normalizeTransport 和 normalizeConfig 首先检查匹配的提供者插件,然后回退到其他钩子能力的提供者插件,直到其中一个实际更改模型 id 或传输/配置。这使别名/兼容提供者 shim 能够工作,而无需调用者知道哪个捆绑插件拥有重写。如果没有提供者钩子重写支持的 Google 族配置条目,捆绑的 Google 配置归一化器仍然应用该兼容性清理。
如果提供者需要完全自定义的连线协议或自定义请求执行器,那是不同类别的扩展。这些钩子用于仍然在 OpenClaw 正常推理循环上运行的提供者行为。
提供者示例
ts
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);
},
});内置示例
捆绑的提供者插件组合上述钩子以适应每个供应商的目录、认证、思考、重播和使用需求。权威钩子集位于各插件下的 extensions/ 中;本页仅说明形状,而非列出完整列表。
透传目录提供者
OpenRouter、Kilocode、Z.AI、xAI 注册 `catalog` 加上 `resolveDynamicModel` / `prepareDynamicModel`,以便它们可以在 OpenClaw 静态目录之前展示上游模型 id。
OAuth 和使用端点提供者
GitHub Copilot、Gemini CLI、ChatGPT Codex、MiniMax、Xiaomi、z.ai 配对 `prepareRuntimeAuth` 或 `formatApiKey` 与 `resolveUsageAuth` + `fetchUsageSnapshot`,以拥有令牌交换和 `/usage` 集成。
重播和转录清理族
共享命名族(`google-gemini`、`passthrough-gemini`、`anthropic-by-model`、`hybrid-anthropic-openai`)让提供者通过 `buildReplayPolicy` 选择转录策略,而不是每个插件重新实现清理。
仅目录提供者
`byteplus`、`cloudflare-ai-gateway`、`huggingface`、`kimi-coding`、`nvidia`、`qianfan`、`synthetic`、`together`、`venice`、`vercel-ai-gateway` 和 `volcengine` 仅注册 `catalog` 并共享通用推理循环。
Anthropic 特定的流助手
Beta 头、`/fast` / `serviceTier` 和 `context1m` 位于 Anthropic 插件的公共 `api.ts` / `contract-api.ts` 接缝中(`wrapAnthropicProviderStream`、`resolveAnthropicBetas`、`resolveAnthropicFastMode`、`resolveAnthropicServiceTier`),而不是通用 SDK 中。
运行时助手
插件可以通过 api.runtime 访问选定的核心助手。对于 TTS:
ts
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 输出载荷,用于文件/语音便笺表面。- 使用核心
messages.tts配置和提供者选择。 - 返回 PCM 音频缓冲区 + 采样率。插件必须为提供者重新采样/编码。
listVoices每个提供者可选。用于供应商拥有的语音选择器或设置流程。- 语音列表可以包含更丰富的元数据,如区域、性别和个性标签,用于提供者感知的选择器。
- OpenAI 和 ElevenLabs 今天支持电话。Microsoft 不支持。
插件也可以通过 api.registerSpeechProvider(...) 注册语音提供者。
ts
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 添加这些能力契约。
对于图像/音频/视频理解,插件注册一个类型化的媒体理解提供者,而不是通用的键/值包:
ts
api.registerMediaUnderstandingProvider({
id: "google",
capabilities: ["image", "audio", "video"],
describeImage: async (req) => ({ text: "..." }),
transcribeAudio: async (req) => ({ text: "..." }),
describeVideo: async (req) => ({ text: "..." }),
});注意:
- 将编排、回退、配置和渠道接线保留在核心中。
- 将供应商行为保留在提供者插件中。
- 加法扩展应保持类型化:新的可选方法、新的可选结果字段、新的可选能力。
- 视频生成已遵循相同模式:
- 核心拥有能力契约和运行时助手
- 供应商插件注册
api.registerVideoGenerationProvider(...) - 功能/渠道插件消费
api.runtime.videoGeneration.*
对于媒体理解运行时助手,插件可以调用:
ts
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,
});
const extraction = await api.runtime.mediaUnderstanding.extractStructuredWithModel({
provider: "codex",
model: "gpt-5.5",
input: [
{
type: "image",
buffer: receiptImageBuffer,
fileName: "receipt.png",
mime: "image/png",
},
{ type: "text", text: "Use the printed fields as the source of truth." },
],
instructions: "Return entities and searchable tags.",
schemaName: "example.evidence",
jsonSchema: {
type: "object",
properties: {
entities: { type: "array", items: { type: "string" } },
tags: { type: "array", items: { type: "string" } },
},
},
cfg: api.config,
});对于音频转录,插件可以使用媒体理解运行时或旧的 STT 别名:
ts
const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
filePath: "/tmp/inbound-audio.ogg",
cfg: api.config,
// 当 MIME 无法可靠推断时可选:
mime: "audio/ogg",
});注意:
api.runtime.mediaUnderstanding.*是图像/音频/视频理解的首选共享表面。extractStructuredWithModel(...)是插件面向的接缝,用于有界的提供者拥有的图像优先提取。至少包含一个图像输入;文本输入是补充上下文。产品插件拥有其路由和 schema,而 OpenClaw 拥有提供者/运行时边界。- 使用核心媒体理解音频配置(
tools.media.audio)和提供者回退顺序。 - 当没有生成转录输出时(例如跳过/不支持的输入),返回
{ text: undefined }。 api.runtime.stt.transcribeAudioFile(...)仍然作为兼容性别名存在。
插件也可以通过 api.runtime.subagent 启动后台子代理运行:
ts
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,
});注意:
provider和model是可选的每次运行覆盖,不是持久的会话更改。- OpenClaw 仅对受信任的调用者遵守这些覆盖字段。
- 对于插件拥有的回退运行,操作员必须通过
plugins.entries.<id>.subagent.allowModelOverride: true选择加入。 - 使用
plugins.entries.<id>.subagent.allowedModels将受信任插件限制到特定规范provider/model目标,或"*"显式允许任何目标。 - 不受信任的插件子代理运行仍然工作,但覆盖请求被拒绝而不是静默回退。
- 插件创建的子代理会话会标记创建插件的 id。回退
api.runtime.subagent.deleteSession(...)可能仅删除那些拥有的会话;任意会话删除仍然需要管理员范围的 Gateway 请求。
对于网络搜索,插件可以消费共享运行时助手,而不是深入到智能体工具接线中:
ts
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.*是功能/渠道插件的首选共享表面,这些插件需要搜索行为而不依赖代理工具包装器。
api.runtime.imageGeneration
ts
const result = await api.runtime.imageGeneration.generate({
config: api.config,
args: { prompt: "A friendly lobster mascot", size: "1024x1024" },
});
const providers = api.runtime.imageGeneration.listProviders({
config: api.config,
});generate(...): 使用配置的图像生成提供者链生成图像。listProviders(...): 列出可用的图像生成提供者及其能力。
Gateway HTTP 路由
插件可以使用 api.registerHttpRoute(...) 暴露 HTTP 端点。
ts
api.registerHttpRoute({
path: "/acme/webhook",
auth: "plugin",
match: "exact",
handler: async (_req, res) => {
res.statusCode = 200;
res.end("ok");
return true;
},
});路由字段:
path: 在 Gateway HTTP 服务器下的路由路径。auth: 必需。使用"gateway"要求正常 Gateway 认证,或"plugin"用于插件管理的认证/webhook 验证。match: 可选。"exact"(默认)或"prefix"。replaceExisting: 可选。允许同一插件替换其自身的现有路由注册。handler: 当路由处理了请求时返回true。
注意:
api.registerHttpHandler(...)已被移除,会导致插件加载错误。使用api.registerHttpRoute(...)。- 插件路由必须显式声明
auth。 - 精确的
path + match冲突会被拒绝,除非replaceExisting: true,且一个插件不能替换另一个插件的路由。 - 不同
auth级别的重叠路由会被拒绝。只在同一 auth 级别保持exact/prefix降级链。 auth: "plugin"路由不会自动接收操作员运行时范围。它们用于插件管理的 webhook/签名验证,而不是特权的 Gateway 助手调用。auth: "gateway"路由在 Gateway 请求运行时范围内运行,但该范围有意保守:- 共享密钥 bearer 认证(
gateway.auth.mode = "token"/"password")将插件路由运行时范围固定为operator.write,即使调用者发送x-openclaw-scopes。 - 受信任的身份携带 HTTP 模式(例如
trusted-proxy或私有入口上的gateway.auth.mode = "none")仅当标头显式存在时才遵守x-openclaw-scopes。 - 如果
x-openclaw-scopes在这些身份携带的插件路由请求中不存在,运行时范围回退到operator.write。
- 共享密钥 bearer 认证(
- 实用规则:不要假设 gateway-auth 插件路由是隐式管理员表面。如果路由需要仅管理员的行为,要求身份携带认证模式,并记录显式的
x-openclaw-scopes标头契约。
插件 SDK 导入路径
编写新插件时,使用窄 SDK 子路径,而不是整体 openclaw/plugin-sdk 根目录 barrel。核心子路径:
| 子路径 | 用途 |
|---|---|
openclaw/plugin-sdk/plugin-entry | 插件注册原语 |
openclaw/plugin-sdk/channel-core | 渠道入口/构建助手 |
openclaw/plugin-sdk/core | 通用共享助手和总括契约 |
openclaw/plugin-sdk/config-schema | 根 openclaw.json Zod schema(OpenClawSchema) |
渠道插件从一系列窄接缝中选择——channel-setup、setup-runtime、setup-tools、channel-pairing、channel-contract、channel-feedback、channel-inbound、channel-lifecycle、channel-reply-pipeline、command-auth、secret-input、webhook-ingress、channel-targets 和 channel-actions。批准行为应合并到一个 approvalCapability 契约上,而不是跨不相关的插件字段混合。参见 渠道插件。
运行时和配置助手位于匹配的聚焦 *-runtime 子路径下(approval-runtime、agent-runtime、lazy-runtime、directory-runtime、text-runtime、runtime-store、system-event-runtime、heartbeat-runtime、channel-activity-runtime 等)。优先使用 config-contracts、plugin-config-runtime、runtime-config-snapshot 和 config-mutation,而不是广泛的 config-runtime 兼容性 barrel。
INFO
openclaw/plugin-sdk/channel-runtime、openclaw/plugin-sdk/config-runtime 和 openclaw/plugin-sdk/infra-runtime 是旧插件的已弃用兼容性 shim。新代码应改为导入更窄的通用原语。
仓库内部入口点(每个捆绑插件包根目录):
index.js— 捆绑插件入口api.js— 助手/类型 barrelruntime-api.js— 仅运行时 barrelsetup-entry.js— 设置插件入口
外部插件应仅导入 openclaw/plugin-sdk/* 子路径。切勿从核心或其他插件导入另一个插件包的 src/*。外观加载的入口点优先使用活动运行时配置快照(当存在时),然后回退到磁盘上的已解析配置文件。
能力特定的子路径如 image-generation、media-understanding 和 speech 存在是因为捆绑插件今天使用它们。它们不自动是长期冻结的外部契约——在依赖它们时检查相关的 SDK 参考页面。
消息工具 schema
对于非消息原语(如反应、读取和投票),插件应拥有渠道特定的 describeMessageTool(...) schema 贡献。共享的发送表示应使用通用 MessagePresentation 契约,而不是提供者本地的按钮、组件、块或卡片字段。参见 消息表示 了解契约、回退规则、提供者映射和插件作者清单。
具备发送能力的插件通过消息能力声明它们可以渲染的内容:
presentation用于语义表示块(text、context、divider、buttons、select)delivery-pin用于固定传递请求
核心决定是原生渲染表示还是降级为文本。不要从通用消息工具暴露提供者原生 UI 逃生舱。已弃用的 SDK 助手用于旧原生 schema 仍然为现有第三方插件导出,但新插件不应使用它们。
渠道目标解析
渠道插件应拥有渠道特定的目标语义。保持共享的出站主机通用,并使用消息适配器表面进行提供者规则:
messaging.inferTargetChatType({ to })决定在目录查找之前应将归一化目标视为direct、group还是channel。messaging.targetResolver.looksLikeId(raw, normalized)告诉核心输入是否应直接跳到类 id 解析,而不是目录搜索。messaging.targetResolver.resolveTarget(...)是插件回退,当核心在归一化后或目录未命中后需要最终提供者拥有的解析。messaging.resolveOutboundSessionRoute(...)在目标解析后拥有提供者特定的会话路由构建。
推荐的分割:
- 使用
inferTargetChatType进行应在搜索对等体/组之前发生的类别决策。 - 使用
looksLikeId进行“将此视为显式/原生目标 id”检查。 - 使用
resolveTarget进行提供者特定的归一化回退,而不是广泛的目录搜索。 - 将提供者原生 id(如聊天 id、线程 id、JID、句柄和房间 id)保留在
target值或提供者特定参数中,而不是通用 SDK 字段。
配置支持的目录
从配置派生目录条目的插件应将逻辑保留在插件中,并使用来自 openclaw/plugin-sdk/directory-runtime 的共享助手。
当渠道需要配置支持的对等体/组时使用,例如:
- 允许列表驱动的 DM 对等体
- 配置的渠道/组映射
- 账户范围的静态目录回退
directory-runtime 中的共享助手仅处理通用操作:
- 查询过滤
- 限制应用
- 去重/归一化助手
- 构建
ChannelDirectoryEntry[]
渠道特定的账户检查和 id 归一化应保留在插件实现中。
提供者目录
提供者插件可以通过 registerProvider({ catalog: { run(...) { ... } } }) 定义推理的模型目录。
catalog.run(...) 返回与 OpenClaw 写入 models.providers 相同的形状:
{ provider }用于一个提供者条目{ providers }用于多个提供者条目
当插件拥有提供者特定的模型 id、baseUrl 默认值或认证门控的模型元数据时,使用 catalog。
catalog.order 控制插件的目录相对于 OpenClaw 内置隐式提供者的合并顺序:
simple:普通 API-key 或 env 驱动的提供者profile:在认证配置文件存在时出现的提供者paired:合成多个相关提供者条目的提供者late:最后一遍,在其他隐式提供者之后
后面的提供者在键冲突时胜出,因此插件可以有意识地使用相同提供者 id 覆盖内置提供者条目。
插件也可以通过 api.registerModelCatalogProvider({ provider, kinds, staticCatalog, liveCatalog }) 发布只读模型行。这是 list/help/picker 表面的前向路径,并支持 text、image_generation、video_generation 和 music_generation 行。提供者插件仍然拥有实时端点调用、令牌交换和供应商响应映射;核心拥有通用行形状、源标签和媒体工具帮助格式化。媒体生成提供者注册会自动从 defaultModel、models 和 capabilities 合成静态目录行。
兼容性:
discovery仍作为遗留别名工作,但会发出弃用警告。- 如果同时注册了
catalog和discovery,OpenClaw 使用catalog。 augmentModelCatalog已弃用;捆绑提供者应通过registerModelCatalogProvider发布补充行。
只读渠道检查
如果插件注册了渠道,建议在 resolveAccount(...) 旁边实现 plugin.config.inspectAccount(cfg, accountId)。
原因:
resolveAccount(...)是运行时路径。它允许假设凭据已完全实体化,并可以在所需密钥缺失时快速失败。- 只读命令路径,如
openclaw status、openclaw status --all、openclaw channels status、openclaw channels resolve以及 doctor/config 修复流程,不应需要实体化运行时凭据来仅描述配置。
推荐的 inspectAccount(...) 行为:
- 仅返回描述性账户状态。
- 保留
enabled和configured。 - 在相关时包含凭据源/状态字段,例如:
tokenSource、tokenStatusbotTokenSource、botTokenStatusappTokenSource、appTokenStatussigningSecretSource、signingSecretStatus
- 不需要仅仅为了报告只读可用性而返回原始令牌值。返回
tokenStatus: "available"(和匹配的源字段)对于状态式命令就足够了。 - 当凭据通过 SecretRef 配置但在当前命令路径中不可用时,使用
configured_unavailable。
这允许只读命令报告“已配置但在该命令路径中不可用”,而不是崩溃或误报账户未配置。
包打包
插件目录可能包含带有 openclaw.extensions 的 package.json:
json
{
"name": "my-pack",
"openclaw": {
"extensions": ["./src/safety.ts", "./src/tools.ts"],
"setupEntry": "./src/setup-entry.ts"
}
}每个条目成为一个插件。如果包列出了多个扩展,插件 id 变为 name/<fileBase>。
如果插件导入 npm 依赖,在该目录中安装它们以使 node_modules 可用(npm install / pnpm install)。
安全防护:每个 openclaw.extensions 条目必须在符号链接解析后保持在插件目录内。转义包目录的条目会被拒绝。
安全说明:openclaw plugins install 使用项目本地的 npm install --omit=dev --ignore-scripts(无生命周期脚本,运行时无开发依赖)安装插件依赖,忽略继承的全局 npm 安装设置。保持插件依赖树为“纯 JS/TS”,避免需要 postinstall 构建的包。
可选:openclaw.setupEntry 可以指向一个轻量级仅设置模块。当 OpenClaw 需要禁用渠道插件的设置表面,或者渠道插件已启用但尚未配置时,它会加载 setupEntry 而不是完整插件入口。当主要插件入口也接入了工具、钩子或其他仅运行时代码时,这使启动和设置更轻量。
可选:openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen 可以允许渠道插件在 Gateway 的监听前启动阶段使用相同的 setupEntry 路径,即使渠道已经配置。
仅当 setupEntry 完全覆盖了 Gateway 开始监听之前必须存在的启动表面时才使用此选项。在实践中,这意味着设置入口必须注册每个渠道拥有的、启动依赖的能力,例如:
- 渠道注册本身
- 任何必须在 Gateway 开始监听之前可用的 HTTP 路由
- 任何在同一窗口期间必须存在的 Gateway 方法、工具或服务
如果完整入口仍然拥有任何必需的启动能力,请不要启用此标志。将插件保留在默认行为上,让 OpenClaw 在启动期间加载完整入口。
捆绑渠道还可以发布仅设置的契约表面助手,核心可以在完整渠道运行时加载之前查阅。当前的设置推广表面是:
singleAccountKeysToMovenamedAccountPromotionKeysresolveSingleAccountPromotionTarget(...)
当核心需要将遗留的单账户渠道配置升级为 channels.<id>.accounts.* 而无需加载完整插件入口时,使用该表面。Matrix 是当前捆绑示例:它仅在命名账户已经存在时将认证/引导密钥移动到一个命名的推广账户,并且可以保留已配置的非规范默认账户密钥,而不是总是创建 accounts.default。
这些设置补丁适配器保持捆绑契约表面发现惰性。导入时间保持轻量;推广表面仅在首次使用时加载,而不是在模块导入时重新进入捆绑渠道启动。
当这些启动表面包括 Gateway RPC 方法时,将它们放在插件特定的前缀上。核心管理命名空间(config.*、exec.approvals.*、wizard.*、update.*)保持保留,并始终解析为 operator.admin,即使插件请求更窄的范围。
示例:
json
{
"name": "@scope/my-channel",
"openclaw": {
"extensions": ["./index.ts"],
"setupEntry": "./setup-entry.ts",
"startup": {
"deferConfiguredChannelFullLoadUntilAfterListen": true
}
}
}渠道目录元数据
渠道插件可以通过 openclaw.channel 宣传设置/发现元数据,并通过 openclaw.install 宣传安装提示。这使核心目录保持无数据。
示例:
json
{
"name": "@openclaw/nextcloud-talk",
"openclaw": {
"extensions": ["./index.ts"],
"channel": {
"id": "nextcloud-talk",
"label": "Nextcloud Talk",
"selectionLabel": "Nextcloud Talk (self-hosted)",
"docsPath": "/channels/nextcloud-talk",
"docsLabel": "nextcloud-talk",
"blurb": "Self-hosted chat via Nextcloud Talk webhook bots.",
"order": 65,
"aliases": ["nc-talk", "nc"]
},
"install": {
"npmSpec": "@openclaw/nextcloud-talk",
"localPath": "<bundled-plugin-local-path>",
"defaultChoice": "npm"
}
}
}超过最小示例的实用 openclaw.channel 字段:
detailLabel:丰富目录/状态表面的辅助标签docsLabel:覆盖文档链接的链接文本preferOver:此目录条目应超越的低优先级插件/渠道 idselectionDocsPrefix、selectionDocsOmitLabel、selectionExtras:选择表面复制控制markdownCapable:标记渠道为支持 Markdown,用于出站格式化决策exposure.configured:当设置为false时,从已配置渠道列表表面隐藏渠道exposure.setup:当设置为false时,从交互式设置/配置选择器隐藏渠道exposure.docs:将渠道标记为内部/私有的文档导航表面showConfigured/showInSetup:为了兼容性仍然接受的遗留别名;优先使用exposurequickstartAllowFrom:选择渠道进入标准快速入门allowFrom流程forceAccountBinding:即使只有一个账户存在,也要求显式账户绑定preferSessionLookupForAnnounceTarget:在解析公告目标时优先使用会话查找
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": {...} } } ] }。解析器也接受 "packages" 或 "plugins" 作为 "entries" 键的遗留别名。
生成的渠道目录条目和提供者安装目录条目会将归一化的安装源事实暴露在旁边。归一化事实标识 npm spec 是精确版本还是浮动选择器,预期的完整性元数据是否存在,以及本地源路径是否也可用。当目录/包标识已知时,归一化事实会警告解析的 npm 包名称是否偏离该标识。当 defaultChoice 无效或指向不可用的源时,以及当 npm 完整性元数据存在但没有有效 npm 源时,它们也会发出警告。消费者应将 installSource 视为可选的附加字段,以便手工构建的条目和目录 shim 不必合成它。这使入门和诊断可以解释源平面状态,而无需导入插件运行时。
官方的外部 npm 条目应首选精确的 npmSpec 加上 expectedIntegrity。裸包名和 dist-tags 仍然为兼容性工作,但它们会展示源平面警告,以便目录可以转向固定、完整性检查的安装,而不会破坏现有插件。当从本地目录路径入门安装时,它会记录一个托管插件插件索引条目,带有 source: "path" 和可能的相对于工作空间的 sourcePath。绝对操作加载路径保持在 plugins.load.paths 中;安装记录避免将本地工作站路径复制到长期配置中。这使本地开发安装对源平面诊断可见,而不会添加第二个原始文件系统路径披露表面。持久化的 plugins/installs.json 插件索引是安装的事实来源,并且可以在不加载插件运行时模块的情况下刷新。它的 installRecords 映射即使插件清单丢失或无效也是持久的;它的 plugins 数组是可重建的清单视图。
上下文引擎插件
上下文引擎插件拥有用于摄入、装配和压缩的会话上下文编排。从插件中使用 api.registerContextEngine(id, factory) 注册它们,然后使用 plugins.slots.contextEngine 选择活动引擎。
当插件需要替换或扩展默认上下文管道,而不仅仅是添加记忆搜索或钩子时使用。
ts
import { buildMemorySystemPromptAddition } from "openclaw/plugin-sdk/core";
export default function (api) {
api.registerContextEngine("lossless-claw", (ctx) => ({
info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
async ingest() {
return { ingested: true };
},
async assemble({ messages, availableTools, citationsMode }) {
return {
messages,
estimatedTokens: 0,
systemPromptAddition: buildMemorySystemPromptAddition({
availableTools: availableTools ?? new Set(),
citationsMode,
}),
};
},
async compact() {
return { ok: true, compacted: false };
},
}));
}工厂 ctx 暴露了可选的 config、agentDir 和 workspaceDir 值,用于构造时初始化。
当活动 harness 有持久后端线程时,assemble() 可能返回 contextProjection。对于遗留的每轮投影则省略它。当装配的上下文应一次性注入到后端线程并重复使用直到 epoch 改变时,返回 { mode: "thread_bootstrap", epoch }。在引擎的语义上下文更改后更改 epoch,例如在引擎拥有的压缩传递后。主机可以在线程引导投影中保留工具调用元数据、输入形状和已编辑的工具结果,以便新的后端线程保持工具连续性而无需复制原始秘密承载载荷。
如果引擎不拥有压缩算法,保持 compact() 实现并显式委托给它:
ts
import {
buildMemorySystemPromptAddition,
delegateCompactionToRuntime,
} from "openclaw/plugin-sdk/core";
export default function (api) {
api.registerContextEngine("my-memory-engine", (ctx) => ({
info: {
id: "my-memory-engine",
name: "My Memory Engine",
ownsCompaction: false,
},
async ingest() {
return { ingested: true };
},
async assemble({ messages, availableTools, citationsMode }) {
return {
messages,
estimatedTokens: 0,
systemPromptAddition: buildMemorySystemPromptAddition({
availableTools: availableTools ?? new Set(),
citationsMode,
}),
};
},
async compact(params) {
return await delegateCompactionToRuntime(params);
},
}));
}添加新能力
当插件需要的行为不适合当前 API 时,不要通过私有伸手来绕过插件系统。添加缺失的能力。
推荐顺序:
- 定义核心契约 决定核心应拥有哪些共享行为:策略、回退、配置合并、生命周期、渠道面向的语义和运行时助手形状。
- 添加类型化的插件注册/运行时表面 使用最小的有用类型化能力表面扩展
OpenClawPluginApi和/或api.runtime。 - 接线核心 + 渠道/功能消费者 渠道和功能插件应通过核心消费新能力,而不是直接导入供应商实现。
- 注册供应商实现 然后供应商插件针对能力注册它们的后端。
- 添加契约覆盖 添加测试,使所有权和注册形状随时间保持显式。
这是 OpenClaw 如何保持有主见而不硬编码到单一提供商世界观的方法。参见 能力食谱 获取具体文件清单和工作示例。
能力清单
当你添加新能力时,实现通常应触及这些表面:
src/<capability>/types.ts中的核心契约类型src/<capability>/runtime.ts中的核心运行器/运行时助手src/plugins/types.ts中的插件 API 注册表面src/plugins/registry.ts中的插件注册表接线src/plugins/runtime/*中的插件运行时暴露(当功能/渠道插件需要消费它时)src/test-utils/plugin-registration.ts中的捕获/测试助手src/plugins/contracts/registry.ts中的所有权/契约断言docs/中的操作员/插件文档
如果其中一个表面缺失,通常表明该能力尚未完全集成。
能力模板
最小模式:
ts
// 核心契约
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.generate({
prompt: "Show the robot walking through the lab.",
cfg,
});契约测试模式:
ts
expect(findVideoGenerationProviderIdsForPlugin("openai")).toEqual(["openai"]);这保持规则简单:
- 核心拥有能力契约 + 编排
- 供应商插件拥有供应商实现
- 功能/渠道插件消费运行时助手
- 契约测试保持所有权显式
相关
- 插件架构 — 公共能力模型和形状
- 插件 SDK 子路径
- 插件 SDK 设置
- 构建插件
常见问题
插件加载顺序怎么调整?
加载顺序由发现根、配置的 load.paths、启用规则(plugins.enabled、allow/deny)以及清单中的 activation.* 提示共同决定。没有直接的手动排序配置;插件按发现顺序处理,但 manifest-first 行为允许通过启动提示缩小加载范围。如果两个插件相同路径匹配,顺序取决于发现顺序。诊断注册表状态可使用 openclaw doctor 或检查启动日志。
HTTP 路由注册冲突怎么解决?
路由注册冲突发生在两个插件注册了相同 path + match(例如两个 exact 路径相同),且未设置 replaceExisting: true。OpenClaw 会拒绝冲突。解决方法是:在插件内使用 replaceExisting: true 替换自己的路由(不能替换其他插件的),或调整 path/match 以避免重叠。同时注意 auth 级别必须一致:gateway 和 plugin 级别的重叠路由会被拒绝。
provider 运行时钩子有哪些,调用顺序是什么?
共有 43 个可选钩子,从 catalog、applyConfigDefaults 到最终 onModelSelected。详细顺序和用途参见本文的钩子顺序与使用表格。常见场景:需要自定义认证时实现 prepareRuntimeAuth;需要自定义模型解析时实现 resolveDynamicModel;需要自定义流传输时实现 createStreamFn。每个钩子的返回值和参数在表格中说明。