Files
sub2api/docs/PLUGIN_DEVELOPMENT.md
T

12 KiB
Raw Blame History

Sub2API 插件开发教程

本文面向希望为 Sub2API 开发、打包和发布插件的团队。插件是独立进程和静态 UI 组成的 .s2plugin 包,宿主通过稳定的 gRPC 协议调用它。本文以当前宿主已经定义的 openai.oauth.outbound_transport.v1 能力作为协议示例,说明开发者需要准备什么、哪些职责属于插件、哪些职责仍由 Sub2API 负责。

本文不是一个可直接安装的完整插件,也不代表 Sub2API 已经发布对应的官方插件包。当前文档主要描述公开协议、宿主边界和开发流程。后续是否发布可安装包、支持哪些 Provider,以及如何提供示例仓库,都需要另行公告。

1. 准备开发环境

建议使用以下环境:

  • Go 1.21 或更高版本;
  • Node.js(仅在插件 UI 使用 JavaScript 时需要);
  • Git
  • 与目标部署环境一致的构建工具链。

协议定义和通用说明位于:

  • backend/pkg/pluginapi/v1/plugin.proto:进程间消息和流式请求定义;
  • backend/pkg/pluginapi/v1/runtime.go:插件进程启动入口;
  • backend/pkg/pluginapi/v1/manifest.schema.json:包清单 JSON Schema
  • backend/pkg/pluginapi/docs/:开发、UI Bridge、包格式和安全边界说明。

目前暂未提供可直接复制的官方示例源码。开发者可以按照本文的目录和协议说明创建自己的插件工程;示例仓库发布后,会在本文补充正式的获取地址、目录说明和版本要求。公开协议始终以 backend/pkg/pluginapi/ 为准。

2. 创建插件工程

在示例仓库发布前,可以先创建一个独立的 Go 工程,目录建议如下:

my-plugin/
├── cmd/<plugin>/main.go
├── internal/pluginconfig/
├── internal/transport/
├── ui/index.html
├── ui/assets/
├── tools/
├── manifest.source.json
└── build.sh

开发时至少准备以下部分:

  1. manifest.source.json:插件 ID、名称、版本、作者、能力和兼容的 Sub2API 版本;
  2. cmd/<plugin>/main.go:启动入口和运行时版本注入,并同步打包器中的构建目标和二进制名称;
  3. internal/pluginconfig/:配置结构、默认值、严格校验和规范化;
  4. internal/transport/:HTTP 客户端、代理、请求头、请求体、网络连接参数、响应流和资源回收;
  5. ui/index.htmlui/assets/:插件自己的配置界面;
  6. 单元测试、进程集成测试和目标平台构建配置。

入口文件应保持很小,只负责调用 pluginv1.Serve。实际逻辑放在可独立测试的包中,避免把配置解析、网络请求和协议组装全部写在 main.go

3. 编写运行时

运行时实现 TransportPlugin 服务,必须满足以下约定:

方法 要求
GetInfo 返回的插件 ID、版本、协议版本、传输 API 版本和能力必须与清单一致。
Health 快速返回进程是否可以接收新请求,不执行长时间网络探测。
ValidateConfig 严格解析 JSON,拒绝未知字段和非法范围,并返回完整的规范化配置。
ApplyConfig 成功后原子切换配置;失败时保留旧配置和旧连接。
TestConfig 针对已保存配置进行快速诊断,返回简短、可展示的结果。
Forward 按协议接收请求流,发出上游请求,再按顺序返回响应流。

请求帧顺序为 start、零到多个 body_chunkbody_end;响应帧顺序为 start、零到多个 body_chunkend。不能继续处理时发送 error 帧。

ForwardResponseError.request_sent 必须准确:只有在能够确认尚未调用上游 HTTP Transport 时才返回 false;一旦已经调用,或无法确认上游是否收到请求,就返回 true。宿主会据此决定是否允许切换账号重试,避免重复执行同一个请求。

资源管理也属于运行时契约:复用 HTTP Transport 和连接池,配置切换时关闭旧空闲连接,沿用 gRPC stream 的 context 取消 DNS、连接、上传和响应读取,并始终关闭上游响应体。日志和错误消息不能包含 Token、代理凭据、完整请求体或敏感响应头。

4. 设计插件配置

插件配置由插件定义,由 Sub2API 加密保存。推荐流程是:

  1. internal/pluginconfig.Config 中定义字段和默认值;
  2. 使用 json.Decoder.DisallowUnknownFields 等严格方式解析;
  3. 将空对象规范化为完整默认配置;
  4. ValidateConfigApplyConfig 中复用同一套校验;
  5. 配置应用成功后再让宿主保存,保存失败时允许恢复旧配置。

JSON 字段统一使用 snake_case。敏感配置不要放入 URL、UI 通知、诊断结果或日志。插件不应从 UI 读取、刷新或持久化 OAuth Token;宿主只在运行时调用需要的网络转发接口。

5. 实现插件自己的配置 UI

UI 是插件包内的静态页面,不需要修改 Sub2API 前端源码。宿主会在受限 iframe 中加载 ui/index.html,并通过 UI Bridge 提供配置读写和测试能力。

页面初始化流程:

  1. 加载包内 HTML、CSS 和 JavaScript
  2. 创建 Bridge 并注册 message 监听;
  3. 发送 sub2api.plugin.ready
  4. 调用 config.load 渲染表单;
  5. 编辑后调用 config.save
  6. 测试前先保存,再调用 config.test
  7. 页面卸载时调用 dispose()

当前 Bridge 支持:

消息 用途
config.load 读取当前配置。
config.save 提交配置,由运行时校验、应用并加密保存。
config.test 运行已保存配置的诊断。
ui.resize 调整配置 iframe 高度。
ui.notify 显示成功、错误或提示消息。

每条消息都必须带 request_id,并校验 event.source、消息来源标识和 Bridge Token。不要依赖 CDN、远程脚本、Cookie 或本地存储。页面需要兼容窄屏和明暗主题,并正确处理加载、保存、测试、超时和未保存状态。

详细信封格式见 backend/pkg/pluginapi/docs/ui-bridge.md。如果后续示例仓库提供可复用的 Bridge SDK,本文会在示例仓库章节补充对应路径和使用方式。

6. 编写包清单

只维护 manifest.source.json,不要手工编辑构建目录中的 manifest.json。至少需要填写:

{
  "schema_version": 1,
  "id": "example.openai.transport",
  "name": "Example OpenAI Transport",
  "version": "0.1.0",
  "requires": {
    "sub2api": ">=0.1.179 <0.2.0",
    "recommended_sub2api_version": "0.1.179",
    "tested_sub2api_versions": ["0.1.179"],
    "plugin_protocol": 1,
    "transport_api": 1,
    "ui_bridge": 1
  },
  "capabilities": [
    {
      "id": "openai.oauth.outbound_transport.v1",
      "platform": "openai",
      "account_type": "oauth"
    }
  ],
  "runtimes": {},
  "ui": { "entrypoint": "ui/index.html" },
  "files": {}
}

打包器会自动填充目标平台运行时、UI 和运行时文件的 SHA-256。清单中的 requires.sub2api 是硬兼容范围;tested_sub2api_versions 应只填写真实验证过的版本;recommended_sub2api_version 用于管理页面展示。当前宿主仅处理 openai.oauth.outbound_transport.v1,声明其他能力不会自动产生新路由。后续增加 Provider 支持时,会在协议、能力清单和宿主路由完成适配后,再补充对应的清单示例。

7. 生成密钥并签名

生产包应始终签名,宿主默认拒绝未签名包。可以使用插件工程中的密钥生成工具生成一对 Ed25519 密钥;示例仓库发布后会提供标准工具和完整命令:

go run ./tools/keygen -out build/keys/my-publisher

生成的 my-publisher.private 只保存在受控的开发机或 CI Secret 中,不能提交到源码仓库、插件包或部署服务器。公钥是 Base64 文本,可以提供给部署者。

插件工程的 build.sh 应调用标准打包器。自定义发布者密钥时必须同时提供 -signing-key-key-id

./build.sh \
  -signing-key /安全目录/my-publisher.private \
  -key-id my-publisher-v1 \
  -output dist/my-openai-plugin.s2plugin

签名覆盖最终 manifest.json 的精确字节;清单中的文件哈希再覆盖运行时和 UI 文件。签名完成后不要重新格式化 manifest.json

部署者在 Sub2API 配置文件中追加公钥:

plugins:
  allow_unsigned: false
  trusted_publishers:
    my-publisher-v1: "BASE64_ED25519_PUBLIC_KEY"

trusted_publishers 是在宿主内置官方公钥之外追加的信任来源,不能覆盖内置公钥。signature.json 中的 key_id 必须与配置键完全一致。密钥轮换时先发布包含新公钥的宿主配置或版本,再发布新签名包,最后再停用旧密钥。

开发阶段如需使用未签名包,只应在隔离的本地环境临时设置 plugins.allow_unsigned: true,测试完成后立即恢复为 false

8. 构建、测试和安装

在插件目录执行:

go test ./... -count=1
node --check ui/assets/bridge-v1.js
node --check ui/assets/app.js
./build.sh
unzip -t dist/*.s2plugin

回到 Sub2API 仓库根目录后,再使用真实构建包运行宿主集成测试:

cd ../..
SUB2API_TEST_PLUGIN_PACKAGE=plugins/my-openai-plugin/dist/my-openai-plugin.s2plugin \
  go test ./backend/internal/service -run '^TestPluginRuntimeIntegration$' -count=1

最低测试集应覆盖配置默认值和边界值、插件身份、请求和响应分块、流式响应、上下文取消、插件退出、代理开关、包哈希、签名、路径安全、目标平台运行时以及 UI Bridge 的加载、保存、测试、错误和超时。

安装后先保持停用,确认清单兼容性、签名和诊断结果,再按账号灰度启用。API Key 账号和未命中灰度的 OAuth 账号继续走 Sub2API 原有路径。

9. 发布前检查清单

  • 插件版本与 GetInfo 返回值一致;
  • requires.sub2api 覆盖范围经过验证,没有未经测试的破坏性版本;
  • tested_sub2api_versions 与实际测试记录一致;
  • 每个支持的平台和架构都有运行时文件;
  • 生产包存在有效 signature.json,公钥已交付部署者;
  • 包中没有私钥、源映射、测试数据、日志和临时文件;
  • UI 不依赖外部资源,也不保存宿主会话信息;
  • 配置切换、请求取消、响应关闭和错误重试语义经过测试;
  • 发布说明包含升级、停用、回滚和兼容版本信息。

10. 常见问题

现象 排查方向
安装提示签名不受信任 检查 signature.json.key_id、Base64 公钥和配置键是否完全一致。
插件显示不兼容 检查 requires.sub2apiplugin_protocoltransport_apiui_bridge
插件进程无法启动 检查目标系统和架构对应的运行时路径、可执行权限和运行用户权限。
配置页无法加载 检查 ui.entrypoint、UI 文件哈希、Bridge Token 校验和 iframe 消息来源。
保存后配置未生效 查看 ValidateConfigApplyConfig 返回的规范化配置和诊断信息。
请求失败后重复执行 检查 ForwardResponseError.request_sent 是否准确反映请求是否可能已发出。

11. 需要扩展能力时

如果新插件需要支持其他 Provider、其他账号类型或新的消息字段,应先扩展并版本化公开协议,再由宿主增加能力匹配和生命周期处理。不要仅通过清单声明一个宿主尚未实现的能力。这样可以让旧插件继续运行,也能让新宿主明确拒绝不兼容的插件。

Sub2API 后续会持续补充更多 Provider 的插件适配说明,包括能力标识、请求和响应契约、配置字段、UI Bridge 使用方式、版本兼容要求以及测试清单。本文会随着这些能力的落地继续更新,Provider 专属章节会放在本节之后。

12. 示例仓库预留

后续计划提供独立的插件示例仓库,用于存放可复用的运行时骨架、UI 组件、打包工具和各 Provider 的最小实现。目前示例仓库尚未准备完成,因此暂不提供地址;正式发布后会在这里补充仓库地址、适用的 Sub2API 版本、示例插件版本和构建说明。