13.2 扩展 API 表面

生成模型:Claude Opus 4.6 (anthropic/claude-opus-4-6) Token 消耗:输入 ~780k tokens,输出 ~70k tokens(本章合计)


上一节我们了解了插件从发现到加载的完整流程。插件一旦被加载,就通过 OpenClawPluginApi 这个"窗口"与 OpenClaw 主程序交互。本节将深入分析这个 API 表面的三个层次:内部扩展 API(extensionAPI.ts)、面向外部的插件 SDK(plugin-sdk/index.ts)、以及插件钩子接口。


13.2.1 src/extensionAPI.ts — 扩展可访问的 API 集

内部扩展 API 的定位

extensionAPI.ts 是一个内部再导出模块(re-export module),它从 OpenClaw 核心模块中精选出扩展可能需要的函数和类型,统一暴露给内部扩展使用:

// src/extensionAPI.ts(完整)
export { resolveAgentDir, resolveAgentWorkspaceDir } from "./agents/agent-scope.ts";
export { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./agents/defaults.ts";
export { resolveAgentIdentity } from "./agents/identity.ts";
export { resolveThinkingDefault } from "./agents/model-selection.ts";
export { runEmbeddedPiAgent } from "./agents/pi-embedded.ts";
export { resolveAgentTimeoutMs } from "./agents/timeout.ts";
export { ensureAgentWorkspace } from "./agents/workspace.ts";
export {
  resolveStorePath, loadSessionStore,
  saveSessionStore, resolveSessionFilePath,
} from "./config/sessions.ts";

这个文件目前只导出了 Agent 运行时会话存储相关的少量接口。它的职责是为内置扩展extensions/ 目录下与主包一起发布的模块)提供直接访问内部模块的能力。

衍生解释Re-export 模式是 TypeScript/JavaScript 中常见的模块组织模式。一个模块不包含任何逻辑,只通过 export { X } from "Y" 语句从其他模块中"挑选"符号重新导出。这样做的好处是:(1) 调用方只需导入一个模块而非散落在多处的实现;(2) 模块作者可以精确控制哪些 API 对外可见,形成稳定的公共接口边界。

与 Plugin SDK 的关系

extensionAPI.tsplugin-sdk/index.ts 看似都是"导出 API",但定位不同:

维度

extensionAPI.ts

plugin-sdk/index.ts

受众

内置扩展(同仓库)

外部第三方插件

导入方式

import { ... } from "../extensionAPI.js"

import { ... } from "openclaw/plugin-sdk"

稳定性

可随主包版本变化

对外承诺的稳定 API

范围

Agent 运行时、会话存储

通道类型、配置 Schema、工具辅助函数等

内置扩展(如 extensions/discord/extensions/slack/)因为和主包在同一个仓库中,可以直接引用内部模块路径;而第三方插件只能通过 openclaw/plugin-sdk 这个包名导入,由 jiti 的 alias 机制映射到实际文件。


13.2.2 插件 SDK(src/plugin-sdk/

SDK 的设计哲学

plugin-sdk/index.ts 是一个 375 行的巨型再导出模块,它是外部插件开发者与 OpenClaw 交互的唯一公共 API。这个设计遵循了"窄接口、宽实现"的原则——内部实现可以自由重构,只要 SDK 表面保持稳定,第三方插件就不会因内部变更而失效。

SDK 导出分类

SDK 导出的内容可以分为以下几类:

(1)通道适配器类型

这是 SDK 中最大的一块,导出了通道插件开发所需的全部类型定义:

这些类型组成了第 11 章介绍的通道适配器抽象层的完整接口。

(2)插件核心类型

插件注册和服务管理相关的核心类型:

(3)配置 Schema 构建器

为各通道提供 Zod Schema,用于配置验证:

这些 Schema 的导出意义重大——扩展插件通常需要在自己的配置验证中复用核心通道的配置结构。例如,一个基于 WhatsApp 的增强插件可以 extends WhatsApp 的 Schema。

(4)通道特定的辅助函数

SDK 为每个核心通道导出了一组辅助函数,让扩展可以复用核心通道的逻辑:

这种设计使得扩展插件不需要"重新发明轮子"——如果你在开发一个 WhatsApp Business 扩展,可以直接复用核心 WhatsApp 通道的账号解析和媒体加载逻辑。

(5)共享基础设施

跨通道通用的工具函数和类型:

PluginRuntime:插件的运行时沙箱

SDK 中最庞大的类型是 PluginRuntime。它是一个巨型对象,包含了插件在运行时可能需要的所有核心函数引用。我们来看它的分层结构:

衍生解释Facade 模式(外观模式)是一种结构型设计模式。它提供了一个简化的接口来访问一个复杂的子系统。PluginRuntime 就是典型的 Facade——它将 OpenClaw 内部散布在数十个模块中的函数统一收拢到一个对象中,插件只需通过 api.runtime.channel.telegram.sendMessageTelegram() 就能调用原本深埋在内部模块中的功能。

PluginRuntime 的设计有一个重要特点:它包含了所有核心通道的操作函数。这意味着一个飞书扩展可以调用 runtime.channel.telegram.sendMessageTelegram() 来向 Telegram 发送消息。这种"跨通道操作"的能力为高级场景(如消息桥接、多通道联动)打开了大门。


13.2.3 插件钩子(Plugin Hooks)接口

钩子系统概览

OpenClaw 的插件钩子系统覆盖了消息处理链路上的所有关键节点。共有 14 种钩子事件,按生命周期域划分为五组:

钩子域
钩子名
执行模式
返回值

Agent

before_agent_start

顺序(串行)

可修改系统提示词

agent_end

并行

before_compaction

并行

after_compaction

并行

Message

message_received

并行

message_sending

顺序(串行)

可修改/取消消息

message_sent

并行

Tool

before_tool_call

顺序(串行)

可修改参数/阻止调用

after_tool_call

并行

tool_result_persist

同步串行

可修改持久化内容

Session

session_start

并行

session_end

并行

Gateway

gateway_start

并行

gateway_stop

并行

表中可以看到一个重要的规律:只有能修改数据的钩子才使用串行执行,纯通知性质的钩子全部并行执行以提高性能。

钩子的两种注册方式

插件有两种方式注册钩子:

方式一:通过 registerHook() 注册传统钩子

这是面向内部钩子系统(src/hooks/)的注册方式,兼容旧版本:

方式二:通过 on() 注册类型安全的钩子

这是推荐的新方式,提供完整的 TypeScript 类型推导:

两种方式的区别:

维度

registerHook()

on()

类型安全

弱(handler 类型宽泛)

强(根据 hookName 推导)

注册目标

内部钩子系统 + 插件注册表

仅插件类型化注册表

优先级

通过 HookEntry 配置

通过 opts.priority 配置

推荐程度

兼容旧代码

✅ 推荐新代码使用

钩子运行器的实现

钩子的执行由 createHookRunner() 工厂函数创建的 HookRunner 负责。它内部有两种执行策略:

优先级排序的实现在 getHooksForName() 中:

关键钩子详解

before_agent_start:注入上下文

这是最强大的钩子之一,允许插件在 Agent 启动前修改系统提示词:

多个插件注册此钩子时,结果会被合并

message_sending:拦截/修改出站消息

允许插件在消息发送前修改内容或取消发送:

before_tool_call:工具调用拦截

允许插件修改工具参数或阻止工具执行:

tool_result_persist:工具结果持久化控制

这是唯一的同步钩子,因为它在会话转录追加的热路径上执行。处理函数不能返回 Promise:

全局钩子运行器

为了让代码库中任何位置都能触发钩子,OpenClaw 使用了全局单例模式:

hasGlobalHooks() 的存在是一个性能优化——调用方可以在构造钩子事件对象之前先检查是否有人监听,避免不必要的对象创建开销。


本节小结

  1. OpenClaw 的扩展 API 分为两层extensionAPI.ts 面向内置扩展,导出 Agent 运行时和会话存储接口;plugin-sdk/index.ts 面向第三方插件,导出完整的通道类型、配置 Schema 和辅助函数。

  2. Plugin SDK 是一个 375 行的再导出模块,遵循"窄接口、宽实现"原则,导出内容分为通道适配器类型、插件核心类型、配置 Schema、通道辅助函数和共享基础设施五大类。

  3. PluginRuntime 是最庞大的运行时 Facade,将分布在数十个内部模块中的功能统一收拢,并支持跨通道操作(如飞书插件调用 Telegram 发送函数)。

  4. 钩子系统包含 14 种事件,分为并行执行(纯通知)和串行执行(可修改)两种模式,tool_result_persist 是唯一的同步钩子。

  5. 推荐使用 on() 方法注册钩子,它提供完整的 TypeScript 类型推导;关键的修改型钩子(before_agent_startmessage_sendingbefore_tool_call)支持结果合并策略。

Last updated