feat(platform): 插件协议系统 - 支持声明式渠道、工作流与参考媒体映射
This commit is contained in:
@@ -1,167 +1,79 @@
|
||||
# 上传请求协议插件
|
||||
# 上传统一插件包
|
||||
|
||||
当前上传入口只安装**声明式请求协议插件**。它把影策统一的文本、图片、视频或音频请求转换为上游 HTTP API,并解析上游响应。素材源、画布节点、工作流、Agent 工具和 UI 扩展仍由应用代码注册,不能通过这里上传 JSON 安装。
|
||||
影策只有一种插件:一个版本化 Manifest 描述一个插件包可以向宿主贡献的全部能力。Provider、工作流、画布节点、媒体转换、素材源、Agent 和命令都写在同一个 `contributes` 下;运行时可以不同,但不会再出现“协议插件”和“UI 插件”两套清单。
|
||||
|
||||
## 1. 准备清单
|
||||
## 1. 包格式
|
||||
|
||||
上传文件必须是 UTF-8 JSON,大小不超过 512 KiB。插件 ID 使用小写 kebab-case;请求路径必须是相对路径;清单不得包含域名、Cookie 或 API Key。
|
||||
上传文件必须是 `.yingce-plugin` ZIP 包,大小不超过 16 MiB。包内必须有根目录 `manifest.json`;不能上传裸 JSON。清单和可选的 Web 运行时代码、静态资源属于同一个版本、权限和生命周期:
|
||||
|
||||
```text
|
||||
my-plugin.yingce-plugin
|
||||
├── manifest.json
|
||||
├── web/entry.js # 可选;必须配合 runtime.web=sandbox 或 worker
|
||||
├── web/assets/... # 可选静态资源
|
||||
└── docs/... # 可选文档
|
||||
```
|
||||
|
||||
后端 Provider 只使用清单中的声明式映射,不执行包内代码。UI/功能代码必须声明 `entry`,由宿主隔离运行时加载;不能把脚本注入主应用页面。当前版本先完成包校验、存储和入口合同,未授权的 Web 代码不会自动进入主页面。
|
||||
|
||||
## 2. 最小清单
|
||||
|
||||
`manifest.json` 必须是 UTF-8 JSON。插件 ID 使用小写 kebab-case。Manifest 不得包含 Cookie、Token 或 API Key。`entry` 只能指向包内 `web/` 文件。
|
||||
|
||||
```json
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"id": "acme-video",
|
||||
"name": "Acme Video API",
|
||||
"version": "1.0.0",
|
||||
"vendor": "Acme",
|
||||
"description": "Acme 视频生成协议适配",
|
||||
"categories": ["video"],
|
||||
"scopes": [
|
||||
"admin.system-channel",
|
||||
"user.custom-channel",
|
||||
"canvas",
|
||||
"creation",
|
||||
"agent"
|
||||
],
|
||||
"contentType": "application/json",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"mapping": "model",
|
||||
"description": "上游模型名"
|
||||
},
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"mapping": "input.prompt",
|
||||
"description": "视频描述"
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"type": "integer",
|
||||
"mapping": "input.seconds",
|
||||
"description": "生成时长,单位为秒"
|
||||
}
|
||||
],
|
||||
"documentation": "# Acme Video API\n\n在这里写完整的 Markdown 接入文档。"
|
||||
},
|
||||
"create": {
|
||||
"method": "POST",
|
||||
"path": "/v1/videos",
|
||||
"fields": {
|
||||
"model": "request.model",
|
||||
"input.prompt": "request.prompt",
|
||||
"input.seconds": "request.duration"
|
||||
}
|
||||
},
|
||||
"poll": {
|
||||
"method": "GET",
|
||||
"path": "/v1/videos/{{taskId}}"
|
||||
},
|
||||
"response": {
|
||||
"taskIdPaths": ["id", "task_id"],
|
||||
"statusPaths": ["status", "data.status"],
|
||||
"messagePaths": ["error.message", "message"],
|
||||
"resultUrlPaths": ["url", "video.url", "data.0.url"],
|
||||
"resultKind": "video"
|
||||
"apiVersion": "yingce.plugin/v1",
|
||||
"id": "acme-comfyui", "name": "Acme ComfyUI", "version": "1.0.0", "author": "Acme", "description": "通过工作流贡献提供视频生成能力",
|
||||
"permissions": ["generation.run", "media.read"],
|
||||
"configuration": { "fields": [{ "name": "apiKey", "type": "secret", "label": "API Token", "required": true }] },
|
||||
"contributes": {
|
||||
"providers": [{
|
||||
"id": "acme-comfyui", "label": "Acme ComfyUI", "capabilities": ["video"], "scopes": ["user.custom-channel", "canvas"],
|
||||
"baseUrl": "https://api.example.com", "auth": { "type": "bearer", "field": "apiKey" },
|
||||
"create": { "method": "POST", "path": "/workflows/{{model}}", "fields": { "prompt": "request.prompt", "duration": "request.duration" } },
|
||||
"poll": { "method": "GET", "path": "/tasks/{{taskId}}" },
|
||||
"response": { "taskIdPaths": ["data.task_id"], "statusPaths": ["data.status"], "resultPaths": ["data.results"], "resultKind": "video", "resultEphemeral": true }
|
||||
}],
|
||||
"workflows": [{
|
||||
"id": "minimax-h3", "label": "MiniMax H3 文生视频", "providerId": "acme-comfyui", "capability": "video",
|
||||
"parameters": [{ "name": "duration", "type": "number", "required": true }, { "name": "resolution", "type": "string", "values": ["720p", "1080p"] }],
|
||||
"defaults": { "duration": 5, "resolution": "720p" }
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`fields` 左侧是上游请求字段路径,右侧是影策请求字段。同步协议只需要 `create` 和 `response`;异步协议再声明 `poll`。清单不会执行 JavaScript,也不能使用 `host:` 执行器。
|
||||
|
||||
### 同步文本响应
|
||||
|
||||
文本模型不一定需要创建任务后轮询。如果上游在创建请求中直接返回正文,可以只声明 `create` 和 `response`,例如:
|
||||
带 Web 功能的插件在同一份清单中增加:
|
||||
|
||||
```json
|
||||
{
|
||||
"create": {
|
||||
"method": "POST",
|
||||
"path": "/v1/chat/completions",
|
||||
"fields": {
|
||||
"model": "request.model",
|
||||
"messages": "request.messages"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"textPaths": ["choices.0.message.content"],
|
||||
"reasoningPaths": ["choices.0.message.reasoning_content"]
|
||||
}
|
||||
"entry": "web/entry.js",
|
||||
"surfaces": ["fullscreen"],
|
||||
"runtime": { "web": "sandbox" }
|
||||
}
|
||||
```
|
||||
|
||||
- `response.textPaths` 按顺序尝试读取文本正文,支持对象路径和数组下标。
|
||||
- `response.reasoningPaths` 是可选的,用于读取推理内容;没有该字段时不会影响正文解析。
|
||||
- 如果没有声明 `statusPaths`,但解析到了文本或媒体结果,宿主会将本次响应视为成功。
|
||||
- `agentResponse` 只负责 Agent 工具请求的响应解析,与普通文本请求的 `response` 相互独立。
|
||||
## 3. Contribution 规则
|
||||
|
||||
## 2. 编写接入文档
|
||||
- `providers` 声明上游能力和字段映射。鉴权、Base URL、超时、私网校验、轮询、下载和计费由宿主负责。
|
||||
- `workflows` 声明一个 provider 下的具体工作流或模型入口。AutoDL/ComfyUI 等工作流型 API 必须在这里扩展,不能为每个工作流新增宿主代码。
|
||||
- `canvasNodes` 声明节点 ID、默认尺寸和 schema。`renderer` 为 `declarative` 时使用宿主 schema renderer;`sandbox` 由隔离运行时承载。
|
||||
- `transforms` 声明媒体或生成请求转换,必须匹配清单权限和受控 runtime。
|
||||
- `assetSources`、`usageObservers`、`agents`、`commands` 和 `importExport` 是同一清单的其他贡献面。
|
||||
|
||||
`metadata.documentation` 是插件详情页展示的 Markdown 正文,不是可选简介。至少写清楚:
|
||||
一个插件可以同时声明多种贡献,但 ID 必须在插件包内唯一,并且所有贡献共享同一个安装、启停、权限和版本生命周期。
|
||||
|
||||
- 接口与鉴权:创建、查询和取消接口的 Method、Path、Header 与 Content-Type。
|
||||
- 模型与参数:可用模型名、类型、必填性、默认值、可选值及字段映射。
|
||||
- 素材限制:数量、格式、大小、时长,以及 URL 可访问性要求。
|
||||
- 请求与响应:提交成功、处理中、完成和失败的真实示例。
|
||||
- 轮询与下载:状态值、建议间隔、超时和结果 URL 有效期。
|
||||
- 错误处理:HTTP 状态、上游错误字段、内容审核和额度限制。
|
||||
## 4. Provider 与工作流
|
||||
|
||||
文档中的路径、模型名和响应字段必须与清单实现一致。详情页支持标题、表格、列表、引用、链接和代码块,并按 GitHub Flavored Markdown 预览。
|
||||
请求字段右侧只能引用影策统一请求,例如 `request.prompt`、`request.model`、`request.duration`、`request.extra`。声明式字段也支持安全的对象/数组路径(例如 `request.images.0.url`)和字符串变换(`|trim`、`|lower`、`|upper`),不执行插件代码。字段解析为空时会省略该字段,适合映射可选参考素材。Provider 如果把 `requiresPublicMediaUrls` 设为 `true`,宿主会在请求前把用户资源转换为短期签名公网 URL。响应映射支持对象路径和数组下标,并可用 `errorPaths` 声明上游错误码路径。同步 provider 只需要 `create` 和 `response`;异步 provider 再声明 `poll`。
|
||||
|
||||
## 3. 上传前检查
|
||||
结果 URL 如果是短期地址,必须设置 `resultEphemeral: true`。宿主会在任务完成后立即下载并保存资源,画布和任务记录只保存影策资源引用。
|
||||
|
||||
| 检查项 | 要求 |
|
||||
| --- | --- |
|
||||
| 标识 | `apiVersion` 为 `v1`,ID 为 kebab-case,名称和版本不为空 |
|
||||
| 能力 | 至少声明一个 `category` 和一个 `scope` |
|
||||
| 请求 | Method 受支持,Path 为相对路径,字段映射来自 `request.*` |
|
||||
| 响应 | 任务 ID、状态、错误和结果地址路径与真实响应一致 |
|
||||
| 文档 | `metadata.documentation` 完整,并与实现同步 |
|
||||
| 安全 | 不包含域名、密钥、Cookie、个人数据或 `host:` 执行器 |
|
||||
## 5. 安全与运行边界
|
||||
|
||||
上传、启用和停用属于管理员写操作。校验失败时插件不会安装;已安装协议停用后,模型配置入口不再提供该协议。
|
||||
- API Key 只在渠道配置中输入和保存,不进入 Manifest、URL、日志或任务正文。
|
||||
- 上传插件不能请求 `host:` 执行器,不能执行任意浏览器脚本。
|
||||
- 外部请求必须通过后端 provider runtime,宿主统一执行 SSRF、防重放、超时、并发、错误和计费策略。
|
||||
- 插件声明的权限是最小权限;没有对应权限的 contribution 不会被激活。
|
||||
|
||||
## 4. Agent 工具请求协议
|
||||
|
||||
如果协议同时服务于画布 Agent,请额外声明 `agent` 和 `agentResponse`。`agent.fields` 的右侧可以访问:
|
||||
|
||||
- `request.model`
|
||||
- `request.extra.agent.chatCompletion.*`
|
||||
- `request.extra.agent.responses.*`
|
||||
|
||||
这样同一个插件可以由字段映射决定第三方的消息、工具定义和工具选择,不需要在前端为某个模型写死 URL 或请求格式。示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent": {
|
||||
"method": "POST",
|
||||
"path": "/v1/chat/completions",
|
||||
"fields": {
|
||||
"model": "request.model",
|
||||
"messages": "request.extra.agent.chatCompletion.messages",
|
||||
"tools": "request.extra.agent.chatCompletion.tools",
|
||||
"tool_choice": "request.extra.agent.chatCompletion.tool_choice"
|
||||
}
|
||||
},
|
||||
"agentResponse": {
|
||||
"textPaths": ["choices.0.message.content"],
|
||||
"reasoningPaths": ["choices.0.message.reasoning_content"],
|
||||
"toolCallsPath": "choices.0.message.tool_calls",
|
||||
"toolCallIdPaths": ["id"],
|
||||
"toolCallNamePaths": ["function.name"],
|
||||
"toolCallArgumentsPaths": ["function.arguments"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`agentResponse` 的路径支持对象和数组下标(例如 `choices.0.message.content`)。第三方返回的 `tool_calls[].function.arguments` 可以是 JSON 字符串,也可以是 JSON 对象;宿主会统一转换为 Agent 合同要求的字符串。Agent 请求仍由后端统一负责鉴权、私网/本机上游校验、超时、日志和计费;插件只负责字段映射与结果解析。
|
||||
|
||||
## 5. 运行边界
|
||||
|
||||
声明式插件是请求协议运行时:它负责第三方 HTTP 方法、相对路径、请求字段映射、同步/异步响应解析、轮询状态、任务 ID、错误消息和结果地址。系统渠道和用户自建渠道只要选择了明确的协议,都会经过后端协议运行时,不依赖前端为某个模型硬编码请求格式。
|
||||
|
||||
宿主负责鉴权、Base URL 版本归一化、私网/本机上游安全校验、出站请求、超时、并发、计费、轮询生命周期、结果下载和任务恢复。内置的 multipart、签名鉴权、特殊媒体下载等协议仍由宿主内置 adapter 承担;上传的第三方插件必须使用声明式字段映射,不能上传可执行代码。
|
||||
上传、启用、停用和卸载都作用于同一个插件 registry record。清单校验失败时整包不会安装,不会留下半激活的 provider、节点或工作流。
|
||||
|
||||
Reference in New Issue
Block a user