插件开发文档
1. 插件包结构
一个插件是一个目录,打包成 ZIP 上传。最小结构如下:
my_plugin/
├── plugin.json # 插件声明(必需)
├── __init__.py # 空文件即可(必需)
├── runner.py # 入口逻辑(必需)
├── provider.py # provider 契约(必需;纯本地插件可为空)
├── credentials.py # 声明 secrets_schema 时必需
└── README.md # 说明文档(可选)ZIP 内根目录必须直接包含 plugin.json,不要多套一层文件夹。
2. plugin.json 契约
Manifest 使用当前 loader 的严格契约。下面的最小示例由仓库中的合同测试直接校验,实际上传时平台会把 plugin_id 和 package_id 改写到你的私有命名空间。
{
"plugin_id": "store.sandbox.example.text_uppercase",
"node_type": "text_uppercase",
"package_id": "store.sandbox.example.text_uppercase",
"version": "1.0.0",
"runtime_contract_version": "1.0.0",
"manifest_schema_version": "1.0.0",
"execution": "server",
"title": "文本转大写",
"category": "文本处理",
"description": "把输入文本转换为大写。",
"available_services": [
"workflow"
],
"inputs": [
{
"id": "text",
"label": "输入文本",
"data_type": "Text",
"path": "input.text"
}
],
"outputs": [
{
"id": "result",
"label": "处理结果",
"data_type": "Text",
"path": "output.result"
}
],
"config_schema": {},
"pricing_schema": {
"pricing_mode": "fixed",
"unit_credits": 1,
"unit_label": "次"
},
"secrets_schema": [],
"provider_policy": {
"provider_key": "developer.local",
"provider_type": "local",
"auth_type": "none",
"timeout_ms": 30000,
"max_retries": 0
},
"capabilities_schema": {},
"style_schema": {
"icon": "type",
"accent": "#6366f1",
"theme": "indigo",
"node_width": 420
},
"asset_schema": {},
"failure_policy": {
"refund": {
"mode": "refund_on_failure"
}
},
"permissions": {},
"metadata": {
"capabilities": [
"workflow.text_transform"
]
},
"compatibility": {
"workflow_runtime": ">=1.0.0",
"service_slug": "workflow"
},
"dependencies": {
"plugins": [],
"python": [],
"providers": []
},
"independent": true,
"entrypoint": {
"module": "runner",
"callable": "run_text_uppercase"
},
"author": {
"name": "示例开发者"
}
}不要手写 ui_schema、connection_schema、output_schema、layout_schema 或 display_schema;它们由 loader 根据端口、配置和样式声明统一派生。未知字段会被拒绝,扩展字段请使用 x_ 前缀。
第三方插件当前按固定调用积分定价,最低单价和作者/平台分成以 开发者中心从服务端加载的当前合同为准,不要依据文档中的历史数字实现结算逻辑。
3. runner.py 入口
入口是一个 async 函数。配置位于 request["node"]["config"],连线输入位于 request["node"]["input"];返回值只能包含 manifest 输出或标准消息字段:
from typing import Any
async def run_text_uppercase(request: dict[str, Any]) -> dict[str, Any]:
node = request.get("node", {}) if isinstance(request.get("node"), dict) else {}
config = node.get("config", {}) if isinstance(node.get("config"), dict) else {}
inputs = node.get("input", {}) if isinstance(node.get("input"), dict) else {}
text = str(inputs.get("text") or config.get("text") or "")
return {
"result": text.upper(),
"message": "处理完成",
"message_code": "TEXT_UPPERCASE_COMPLETED",
}返回未在 outputs 或派生 output_schema 中声明的业务字段会直接触发契约错误。runner.py 不要自行读取平台环境变量或密钥。
4. 安全限制(审核会拦截)
为保护平台与其他用户,插件代码禁止以下行为,自动审核会直接拦截:
- 导入 os / sys / subprocess / socket / shutil 等系统模块
- 访问 os.environ、读取环境变量或平台密钥
- 调用 eval / exec / open / __import__ 等危险函数
- 读写本地文件系统、启动子进程
允许:httpx(HTTP 调用)、json / typing / datetime / re / base64 / hashlib / math / decimal / uuid / collections / itertools,以及 app.core.errors、app.plugins.toolkit。包内模块使用相对导入;其他导入不会因未列入禁止项而自动获得平台能力。
API Key 等用户凭据必须通过 secrets_schema 声明,并在 permissions.secrets 显式授权;用户密钥不得写入 config_defaults、源码、README 或 ZIP 示例。
5. 提交与审核流程
- 打包成 ZIP,在开发者提交向导上传到私有沙箱(仅你可见)
- 运行私有加载测试,复用正式 loader,校验 manifest、入口、端口、定价与凭据契约;该步骤不会执行 runner,也不会写入公共 registry
- 运行审核并提交,执行确定性的契约校验、AST 危险能力扫描和 Bandit 高危扫描;流程不包含 AI 预审
- 全部通过后状态变为 manual_review 并进入人工终审;任一检查失败则为 rejected,修复后需重新上传、测试并提交
- 人工批准后复制到 approved 区并热重载;只有加载验证成功才更新为 approved
6. 收益与分成
插件上架并产生真实收费调用后,调用积分进入待结算流水;周期结算完成后才计入作者余额。失败调用、退款与历史结算以服务端不可变流水为准。
当前作者分成、平台比例和最低单价由开发者中心从收益 API 的权威合同字段展示。文档页不复制业务常量,避免合同调整后产生第二套口径。