feat: publish v1.0.0-rc.36-custom.1

This commit is contained in:
Codex committed 2026-09-11 05:07:48 +08:00
commit 3a7e8f3b54
2381 files changed
+551494

No files matched your search

+176
View File
@@ -0,0 +1,176 @@
# 用户鉴权与登录会话
面板鉴权采用短期 Access Token、HttpOnly Refresh Cookie 与服务端登录会话控制面的组合。面板请求不再依赖 Gin session,也不再要求 `New-Api-User` 请求头。
## 鉴权模型
- Access Token 是有效期 15 分钟的 JWT,只保存在浏览器内存中,通过 `Authorization: Bearer <token>` 发送。
- Refresh Token 是随机不透明值,有效期最长 30 天。浏览器只通过 `HttpOnly`、`SameSite=Strict` Cookie 持有它;服务端仅保存 HMAC 摘要,并在每次刷新时轮换。
- `new_api_has_session` 是 Refresh Cookie 的会话提示,值恒为 `1`,`Path=/`、非 `HttpOnly`,与 Refresh Cookie 同时写入、同时清除、同一过期时间。它只声明"曾签发过 Refresh Cookie",不含任何凭据,也不参与任何鉴权判定;伪造它唯一的效果是自费一次注定失败的 refresh。它存在的原因是 Refresh Cookie 被 `HttpOnly` 和 `Path=/api/user/auth` 双重限制,`/` 上的页面无法判断自己是否匿名,否则每次冷启动都要发一次注定 401 的 refresh,而该请求还会占用按 IP 计数的 `CriticalRateLimit` 配额。
- `user_sessions` 是登录会话控制面,记录设备、IP、登录方式、最后活跃时间、到期时间和撤销状态。数据库中的 Session 状态是最终权威;撤销传播速度取决于下文所述的 Redis 拓扑。
- 用户的密码、状态、角色或安全因子发生安全相关变化时,`auth_version` 会递增并使旧登录会话失效。订阅带来的分组升降级只刷新授权缓存,不会退出任何登录设备。
- Redis 缓存保存用户鉴权快照和登录会话快照。版本栅栏和撤销 tombstone 防止旧缓存重新授权;Session 快照使用跟随 `SYNC_FREQUENCY` 的短 TTL,缓存未命中或未启用 Redis 时回退到数据库校验。
`SESSION_SECRET` 用于派生 Access Token、Security Proof、Refresh Token 摘要和 AuthFlow 摘要的不同用途密钥。生产环境及多节点部署必须在所有节点配置相同的高强度随机值;更换该值会使现有登录、临时鉴权流程和 Security Proof 全部失效。
## 多节点 Redis 拓扑
多节点部署必须共用同一主数据库。登录 Session、账户级活跃 Session 上限和签发窗口计数都以数据库为权威,因此这些限制在应用节点间全局生效。Redis 中的 Session Hash(包含 `revoking`/`revoked` tombstone)只是缓存,其 TTL 为 Session 剩余寿命与有效 `SYNC_FREQUENCY` 中的较小值;`SYNC_FREQUENCY` 默认及非法值回退均为 `60` 秒。读取缓存不会续期,过期后会按 SID 回源数据库。延迟完成的 active 缓存回写只能使用其数据库观察窗口尚未消耗的 TTL,不能在撤销 tombstone 到期后重新启动一个完整缓存周期。
| Redis 部署方式 | Session 状态传播 | 限流语义 |
| --- | --- | --- |
| 所有节点共享 Redis | 正常撤销和版本发布通过同一缓存即时传播 | Redis 限流额度在所有节点间共享 |
| 每个节点使用独立 Redis | 最迟在该节点 Session 缓存 TTL 到期后回源收敛,即不超过有效 `SYNC_FREQUENCY`;版本轮换期间,新 Token 在持有旧缓存的节点上可能短暂返回 401 | 每个节点独立计数,集群总额度最坏约为单节点阈值乘以节点数 |
| 不使用 Redis | 每次 Session 校验直接读取数据库 | 使用各节点的内存限流器,额度同样按节点独立 |
`SYNC_FREQUENCY` 越大,独立 Redis 部署的陈旧窗口越长;值越小,每个活跃 SID 在每个节点上回源数据库的频率越高。默认配置下,持续活跃的 Session 每个节点最多约每 60 秒增加一次数据库主键点查。共享 Redis 时,撤销 tombstone 和版本发布仍保持即时传播。
所有节点必须使用相同的 `SESSION_SECRET`。当多个节点连接同一个 Redis 时,还必须使用相同的 `CRYPTO_SECRET`,否则节点生成的缓存键摘要不一致,无法正确共享缓存。上述保证只覆盖登录 Session 鉴权的有界陈旧语义;限流额度及其他 Redis 缓存仍会受到 Redis 拓扑影响,不能据此认为整个控制面与拓扑无关。
## 浏览器接口
登录成功后,密码登录、2FA、Passkey、OAuth、WeChat 和 Telegram 登录均返回统一数据:
```json
{
"success": true,
"data": {
"access_token": "...",
"token_type": "Bearer",
"access_expires_at": 1730000000,
"user": {},
"session": {
"sid": "...",
"current": true,
"login_method": "password",
"ip": "...",
"user_agent": "...",
"created_at": 1730000000,
"last_active_at": 1730000000,
"expires_at": 1732592000
}
}
}
```
会话相关接口:
| 接口 | 鉴权 | 用途 |
| --- | --- | --- |
| `POST /api/user/auth/refresh` | Refresh Cookie;Secure 模式附加 Origin 校验 | 轮换 Refresh Token 并签发新的 Access Token |
| `POST /api/user/auth/logout` | Refresh Cookie;Secure 模式附加 Origin 校验,可同时携带 Bearer | 撤销当前登录会话并清除 Cookie |
| `GET /api/user/sessions` | Bearer | 查看当前鉴权版本的有效登录会话,当前会话优先,最多 100 条 |
| `DELETE /api/user/sessions/:sid` | Bearer | 撤销指定登录会话,包括当前会话 |
| `POST /api/user/sessions/revoke-others` | Bearer | 保留当前会话并撤销其他会话 |
客户端内存中已有会话时,应在 refresh/logout 请求中发送 `X-Auth-Session: <sid>`。Refresh Cookie 与该 SID 不一致时,两个端点都返回 `409 AUTH_SESSION_MISMATCH`,且不会轮换、撤销或清除任何会话;客户端先通过 refresh 清除本标签页的旧 SID、恢复 Cookie 当前对应的会话,再重试 logout。冷启动尚无内存会话时可以省略该请求头。
并发使用同一个 Refresh Token 时,服务端通过确定性轮换恢复同一个后继 Token,多个浏览器标签页不会因丢失“胜者”响应而被迫退出。最近一代 Refresh Token 在短暂容错窗口结束后再次出现会撤销对应会话;无法识别的更早代或随机 Token 只会被拒绝,不会允许攻击者凭猜测踢掉会话。
前端使用 Web Locks 串行化同一浏览器配置文件中的刷新,并通过 BroadcastChannel(不支持时回退到 `storage` 事件)仅同步会话标识和登录/退出事件;Access Token 与 Refresh Token 都不会通过跨标签页消息传递或持久化到 Web Storage。
前端将冷启动状态与登录状态分开管理。网络或服务端临时故障允许后续导航重试 refresh;服务端确认 Refresh Cookie 无效时才进入已完成的匿名状态。内存 SID 与 Cookie SID 不一致时,客户端清除旧内存身份并在不携带旧 SID 的情况下重试一次。
公开页面的冷启动会先读 `new_api_has_session`:提示不存在且内存中没有任何身份时跳过 refresh,直接按匿名渲染,且**不**把这次跳过记为已完成的匿名判定——跳过只是延后,不是服务端结论。会依据鉴权结果做跳转的位置(受保护路由与登录页)不看提示,内存为空时一律回源。因此提示缺失但 Refresh Cookie 有效的用户(该 Cookie 上线前建立的会话,或只清理了 `/` 站点数据的浏览器)会在公开页显示为匿名,并在进入上述任一位置时自动恢复登录态,不需要重新输入密码。提示因服务端撤销而过期时,那次 refresh 返回 401 并在同一响应里清除提示,浪费的请求只发生一次。
## Session 签发限额与保留策略
服务端在所有登录方式的统一 Session 签发出口执行两级账户限制:
- `USER_SESSION_ACTIVE_LIMIT`(默认 `50`):单用户未过期且状态为 active 的 Session 上限。达到上限时新登录返回 `409 AUTH_SESSION_LIMIT`。
- `USER_SESSION_ISSUANCE_LIMIT`(默认 `100`)和 `USER_SESSION_ISSUANCE_WINDOW_SECONDS`(默认 `86400`):统计窗口内该用户创建的所有 Session,包含已撤销和旧鉴权版本的记录。达到上限时返回 `429 AUTH_SESSION_ISSUANCE_LIMIT`。
- 这两次计数与插入不加跨数据库锁;极端并发登录可能出现少量超额,但计数失败会拒绝签发,不会降级放行。
升级时已经超过活跃上限的账户不会被自动下线或挤掉旧会话;限制只作用于后续的新 Session 签发。
`USER_SESSION_REVOKED_RETENTION_DAYS`(默认 `7`)控制 revoked 行的审计保留期。签发计数依赖窗口内的行仍存在,因此签发窗口不得超过 revoked 保留期。如果配置超出,启动时会记录告警并将实际窗口钳制到保留期,避免提前删除 revoked 行导致限流计数被低估。
定时清理即使发现 `expires_at` 已过期,也不会删除 `created_at` 仍落在实际签发窗口内的行;尚未达到 revoked 保留期的撤销记录同样会继续保留。这样在扩大配置窗口时,过期清理不会静默削弱签发计数或审计保留。
活跃数量会计入状态仍为 active 但 `user_auth_version` 已过期的异常残留行,而设备列表只展示当前鉴权版本。因此遇到 `AUTH_SESSION_LIMIT` 时,应优先在仍已登录的设备上执行“撤销其他会话”,该操作会同时清理不可见的旧版本 active 行;没有可用设备时可使用密码重置撤销所有会话。密码重置不会清空签发窗口计数。
仅 master 节点每小时分批删除过期 Session 和超过保留期的 revoked Session。`USER_SESSION_HOURLY_ALERT_THRESHOLD`(默认 `5000`)只在最近一小时全局签发量异常时记录告警,不会形成可被滥用的全站登录拒绝开关。
## Refresh/Logout 的 Origin 校验
refresh/logout 的 Origin 防护与 Refresh Cookie 的 Secure 模式绑定:
- 未配置 `SESSION_COOKIE_SECURE` 或显式设为 `false` 时,Refresh Cookie 可用于本地 HTTP,refresh/logout 的 OriginGuard 关闭,并且不得配置 `SESSION_COOKIE_TRUSTED_URL`。这使 `http://localhost` 上不同端口的 Rsbuild/Vite 开发代理可以正常转发请求。该模式仅用于可信的本地开发环境,不应暴露到公网。
- `SESSION_COOKIE_SECURE=true` 时,Refresh Cookie 仅通过 HTTPS 发送,同时启用严格 OriginGuard。`POST /api/user/auth/refresh` 和 `POST /api/user/auth/logout` 会校验浏览器的 `Origin`;缺少 `Origin` 时只接受合法的单一 `Referer` 作为回退。允许来源包括请求自身的精确 Origin,以及 `SESSION_COOKIE_TRUSTED_URL` 中配置的精确 Origin。
Secure 模式的 Origin 校验不信任客户端直接发送的 `X-Forwarded-Proto`。TLS 在反向代理终止时,应将面板的公开 HTTPS Origin 明确写入 `SESSION_COOKIE_TRUSTED_URL`。
`SESSION_COOKIE_TRUSTED_URL` 现在具有明确的新语义:它是 refresh/logout Cookie 端点的可信 Origin 列表,不是 CORS 白名单。配置规则如下:
- 仅在 `SESSION_COOKIE_SECURE=true` 时配置;多个值用英文逗号分隔。
- 每项必须是精确的 HTTPS Origin,例如 `https://panel.example.com` 或 `https://panel.example.com:8443`。
- 不接受通配符、路径、查询参数、用户信息或域名后缀匹配。
- 不会修改 relay、旧 billing dashboard、`/api/usage/token` 或 `/api/log/token` 的 CORS 行为。浏览器使用 `sk-` key 直连 relay 的场景保持不变。
本地 HTTP 开发示例(OriginGuard 关闭):
```env
SESSION_SECRET=<local-random-value>
SESSION_COOKIE_SECURE=false
# SESSION_COOKIE_TRUSTED_URL 不得设置
```
生产 HTTPS 示例(OriginGuard 开启):
```env
SESSION_SECRET=<high-entropy-random-value>
SESSION_COOKIE_SECURE=true
SESSION_COOKIE_TRUSTED_URL=https://panel.example.com,https://admin.example.com
```
该开关只控制面板 Refresh Cookie 和 refresh/logout 的 OriginGuard,不会修改 relay、旧 billing dashboard、`/api/usage/token` 或 `/api/log/token` 的 CORS 行为。
## 可信代理与 IP 限流
Gin 默认会信任所有代理提供的客户端 IP 请求头。本项目改为兼顾常见反代拓扑和公网直连安全的三态配置:
- 未配置、空字符串或纯空白的 `TRUSTED_PROXIES` 默认信任 `127.0.0.0/8`、`::1`、`10.0.0.0/8`、`172.16.0.0/12`、`192.168.0.0/16` 和 `fc00::/7`,并输出启动告警。该默认值覆盖同机 Nginx、Docker Compose 和常见内网反代;公网直连地址不在列表中,其伪造的 `X-Forwarded-For` 会被忽略。
- `TRUSTED_PROXIES=none`(大小写不敏感且必须单独使用)启用严格直连模式,不信任任何代理,`ClientIP()` 只使用 TCP 直连地址。
- 其他非空值按英文逗号解析为代理 IP/CIDR,并完全替代默认列表。应填写反向代理自身的地址而不是客户端网段;非法 CIDR、空列表或将 `none` 与其他值混用都会阻止服务启动。
Gin 只在请求的直连来源属于可信代理时解析客户端 IP 请求头,并从转发链右侧向左寻找首个非可信地址。因此常见 Nginx `$proxy_add_x_forwarded_for` 链中的公网客户端地址会阻止更左侧的伪造前缀生效。默认信任私网的残余风险是:能够从同一私网直接访问应用的其他机器或容器仍可伪造这些请求头;需要消除此风险时应使用 `none` 或配置精确代理地址。
Redis 限流使用原子 Lua 固定窗口,替代旧的近似滑动窗口 List 实现。这是有意的语义变化:窗口边界两侧可分别打满一次,极短时间内通过量最高约为配置值的两倍。例如 `20 次/20 分钟` 在边界可通过约 40 次。帐户级 Session 上限和签发窗口继续控制数据库增长;如未来需要严格抑制边界突发,需单独迁移为 ZSET 滑动窗口。
用户级模型成功请求限流仍使用原有 Redis List 近似滑动窗口,但列表时间戳统一写为 UTC。滚动升级期间,旧节点写入的本地时间字符串和新节点写入的 UTC 字符串无法从格式上区分,可能在一个模型限流窗口内临时误放行或误拒绝。所有节点升级完成并经过一个完整窗口后会自然收敛;本次升级不会切换 Key 或主动删除现有列表。
开放注册仍会受 Critical IP 限流保护,但分布式 IP 多账号攻击不能仅靠 IP 限流阻止。公网开放注册的部署应同时启用 Turnstile 和邮箱验证;更强的设备或多维风控需作为独立安全项目设计。
## PAT 调用契约
`User.AccessToken`(面板 PAT)继续支持 `Authorization: Bearer <pat>`,也兼容原有的单值 `Authorization: <pat>`。`New-Api-User` 不再参与鉴权,外部脚本不需要再发送 Bearer 与用户 ID 双请求头。这是有意的调用契约简化;旧 PAT 本身无需重新生成。
PAT 不是浏览器登录会话,不能调用登录会话管理接口,也不能签发绑定具体登录会话的 Security Proof。
## 临时鉴权流程与二次验证
OAuth state、2FA pending、Passkey ceremony、Telegram bind 等临时状态存放在 `auth_flows`。客户端只持有随机 `flow_token`,数据库仅保存 HMAC 摘要;流程具有用途、provider、intent、用户和登录会话绑定,并且只能原子消费一次。OAuth 注册的 affiliate code 也随登录 AuthFlow 保存。
标准 OAuth 绑定回调由 popup 通过同源 `postMessage` 交给 opener;只有 opener 使用自身内存中的 Bearer 调用后端绑定接口。Telegram 绑定先由已登录前端创建绑定 AuthFlow,再让 widget 回调携带路径中的 `flow_token`,回调时会重新确认原登录会话仍有效。Telegram 的已签名 widget assertion 也会登记为一次性凭据,重复回放会被拒绝。
敏感操作使用有效期 5 分钟的 `X-Security-Proof`:
- `channel.key.read`:查看渠道密钥;
- `passkey.register`:注册 Passkey;
- `passkey.delete`:删除 Passkey。
Proof 同时绑定用户、登录会话、用户鉴权版本、会话版本和 scope,不能跨用户、跨会话或跨用途复用。
启用了 2FA 的用户注册 Passkey 时,register begin 与 finish 都必须携带有效的 `passkey.register` Proof;finish 会在消费一次性 AuthFlow 之前重新验证 Proof。未启用 2FA 的首次 Passkey 注册不要求该请求头。
## 升级注意事项
- 旧 `session` Cookie 不再使用;升级后现有面板登录会失效,用户需要重新登录。
- 数据库迁移会新增 `user_sessions`、`auth_flows`、`external_identity_claims` 和 `users.auth_version`,并为已有用户初始化鉴权版本、回填 Telegram 账号唯一归属;若历史数据中同一 Telegram ID 已绑定多个用户,迁移会拒绝继续启动,需先消除歧义。
- 数据库迁移会为 Session 签发计数和分批清理新增索引;已有 `user_sessions` 很大时应为首次启动预留维护窗口。
- `user_sessions.previous_refresh_hash` 会从定长 `char(64)` 迁移为 `varchar(64)`。应用会兼容读取历史定长字段留下的空格填充;迁移后的目标结构必须保持幂等,连续启动不应反复执行列类型变更。
- 仅 master 节点定时清理过期登录会话、超过配置保留期的 revoked 会话和已过保留期的 AuthFlow。
- 未配置 `TRUSTED_PROXIES` 时会兼容信任回环和常见私网代理;使用公网负载均衡器、`100.64.0.0/10`、链路本地地址或自定义 CNI 网段的部署仍需显式配置。需要严格忽略所有转发头时设置为 `none`。
- Redis 限流从近似滑动窗口改为原子固定窗口,存在明确的边界双倍突发语义。
- 用户级模型成功请求限流的 UTC 时间戳在滚动升级期间存在一个窗口的混合格式过渡,期间可能临时误放行或误拒绝。
- 自建客户端应按新的 AuthBundle、`flow_token` 和 Security Proof 契约升级;PAT 客户端可直接移除 `New-Api-User`。
+41
View File
@@ -0,0 +1,41 @@
# 渠道额外设置说明
该配置用于设置一些额外的渠道参数,可以通过 JSON 对象进行配置。主要包含以下三个设置项:
1. force_format
- 用于标识是否对数据进行强制格式化为 OpenAI 格式
- 类型为布尔值,设置为 true 时启用强制格式化
2. proxy
- 用于配置网络代理
- 类型为字符串,支持 `http`、`https`、`socks5` 和 `socks5h` 协议
- 保存时必须包含协议和主机;仅允许空路径或根路径 `/`,不允许 query 或 fragment
- SOCKS 代理未填写端口时,运行时使用默认端口 `1080`
3. thinking_to_content
- 用于标识是否将思考内容`reasoning_content`转换为`<think>`标签拼接到内容中返回
- 类型为布尔值,设置为 true 时启用思考内容转换
--------------------------------------------------------------
## JSON 格式示例
以下是一个示例配置,启用强制格式化并设置了代理地址:
```json
{
"force_format": true,
"thinking_to_content": true,
"proxy": "socks5://proxy.example:1080"
}
```
--------------------------------------------------------------
通过调整上述 JSON 配置中的值,可以灵活控制渠道的额外行为,比如是否进行格式化以及使用特定的网络代理。
## 升级兼容性
旧版本会忽略代理地址中的 path、query 和 fragment。为避免升级后中断已有渠道流量,运行时会继续剥离这些遗留后缀,并对同一代理地址每个进程记录一次不含凭证和后缀的警告。该兼容逻辑不会改写数据库;再次保存渠道时必须按上述严格规则修正代理地址。
代理连接使用 30 秒 TCP 拨号超时和 30 秒 KeepAlive;TLS 握手超时为 10 秒。这些超时同样适用于未配置渠道代理的中转请求。
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+151
View File
@@ -0,0 +1,151 @@
# 宝塔面板部署教程
本文档提供使用宝塔面板 Docker 功能部署 New API 的图文教程。
> 📖 官方文档:[宝塔面板部署](https://docs.newapi.pro/zh/docs/installation/deployment-methods/bt-docker-installation)
***
## 前置要求
| 项目 | 要求 |
| ----- | ---------------------------------- |
| 宝塔面板 | ≥ 9.2.0 版本 |
| 推荐系统 | CentOS 7+、Ubuntu 18.04+、Debian 10+ |
| 服务器配置 | 至少 1 核 2G 内存 |
***
## 步骤一:安装宝塔面板
1. 前往 [宝塔面板官网](https://www.bt.cn/new/download.html) 下载适合您系统的安装脚本
2. 运行安装脚本安装宝塔面板
3. 安装完成后,使用提供的地址、用户名和密码登录宝塔面板
***
## 步骤二:安装 Docker
1. 登录宝塔面板后,在左侧菜单栏找到并点击 **Docker**
2. 首次进入会提示安装 Docker 服务,点击 **立即安装**
3. 按照提示完成 Docker 服务的安装
***
## 步骤三:安装 New API
### 方法一:使用宝塔应用商店(推荐)
1. 在宝塔面板 Docker 功能中,点击 **应用商店**
2. 搜索并找到 **New-API**
3. 点击 **安装**
4. 配置以下基本选项:
- **容器名称**:可自定义,默认为 `new-api`
- **端口映射**:默认为 `3000:3000`
- **环境变量**:
- `SESSION_SECRET`:会话密钥(**必填**,多机部署时必须一致)
- `CRYPTO_SECRET`:加密密钥(使用 Redis 时必填)
5. 点击 **确认** 开始安装
6. 等待安装完成后,访问 `http://您的服务器IP:3000` 即可使用
### 方法二:使用 Docker Compose
1. 在宝塔面板中创建网站目录,如 `/www/wwwroot/new-api`
2. 创建 `docker-compose.yml` 文件:
```yaml
version: '3'
services:
new-api:
image: calciumion/new-api:latest
container_name: new-api
restart: always
ports:
- "3000:3000"
volumes:
- ./data:/data
environment:
- SESSION_SECRET=your_session_secret_here # 请修改为随机字符串
- TZ=Asia/Shanghai
```
1. 在终端中进入目录并启动:
```bash
cd /www/wwwroot/new-api
docker-compose up -d
```
***
## 配置说明
### 必要环境变量
| 变量名 | 说明 | 是否必填 |
| ------------------- | ------------------ | ------ |
| `SESSION_SECRET` | 会话密钥,多机部署必须一致 | **必填** |
| `CRYPTO_SECRET` | 加密密钥,使用 Redis 时必填 | 条件必填 |
| `SQL_DSN` | 数据库连接字符串(使用外部数据库时) | 可选 |
| `REDIS_CONN_STRING` | Redis 连接字符串 | 可选 |
### 生成随机密钥
```bash
# 生成 SESSION_SECRET
openssl rand -hex 16
# 或使用 Linux 命令
head -c 16 /dev/urandom | xxd -p
```
***
## 常见问题
### Q1:无法访问 3000 端口?
1. 检查服务器防火墙是否开放 3000 端口
2. 在宝塔面板 **安全** 中放行 3000 端口
3. 检查云服务器安全组是否开放端口
### Q2:登录后提示会话失效?
确保设置了 `SESSION_SECRET` 环境变量,且值不为空。
### Q3:数据如何持久化?
使用 Docker 卷映射数据目录:
```yaml
volumes:
- ./data:/data
```
### Q4:如何更新版本?
```bash
# 拉取最新镜像
docker pull calciumion/new-api:latest
# 重启容器
docker-compose down && docker-compose up -d
```
***
## 相关链接
- [官方文档](https://docs.newapi.pro/zh/docs/installation)
- [环境变量配置](https://docs.newapi.pro/zh/docs/installation/config-maintenance/environment-variables)
- [常见问题](https://docs.newapi.pro/zh/docs/support/faq)
- [GitHub 仓库](https://github.com/QuantumNous/new-api)
***
## 截图示例
![宝塔面板 Docker 安装](https://github.com/user-attachments/assets/7a6fc03e-c457-45e4-b8f9-184508fc26b0)
> ⚠️ 注意:密钥为环境变量 `SESSION_SECRET`,请务必设置!
+7
View File
@@ -0,0 +1,7 @@
Request URL
https://api.io.solutions/v1/io-cloud/clusters/654fc0a9-0d4a-4db4-9b95-3f56189348a2/update-name
Request Method
PUT
{"status":"succeeded","message":"Cluster name updated successfully"}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+138
View File
@@ -0,0 +1,138 @@
# Task plugin API v1
Task plugins are single-file synchronous ECMAScript modules. The plugin contract
is currently unreleased; [`v1.schema.json`](./v1.schema.json) and
[`v1.d.ts`](./v1.d.ts) are the authoritative v1 contract.
## Contract and lifecycle
Every plugin exports `meta`, `buildSubmitRequest`, `parseSubmitResponse`, and
`parseTaskResult`. A `per_task` plugin also exports `buildQueryRequest`; a
`batch` plugin exports `buildBatchQueryRequest` and `parseBatchResult`.
`meta.author.name` is required and `meta.author.url`, when present, must be an
absolute HTTP(S) URL. This is self-declared attribution; a future marketplace's
verified publisher identity is a separate host-owned record.
Plugins may declare authenticated vendor-native `meta.routes` and claim
host-owned names through `meta.protocols`. Submit and dynamic routes name a
`native` decoder and presenter; query routes name only a presenter. Protocol
bindings are registered once by the host registry, and protocol decoders receive
the host-parsed `body` union plus the pinned model. Shared protocol hooks are
synchronous transformations; Go owns connections and wire framing.
The host selects a channel, invokes the request-building hook, validates the
returned URL against the channel host, performs HTTP, and gives the decoded
response to the matching parse hook. It owns persistence, retries, polling,
billing, and settlement. Plugins only transform data and report usage facts.
See [v1.d.ts](./v1.d.ts) for signatures and
[v1.schema.json](./v1.schema.json) for machine-readable shapes.
Plugins that expose task outputs export `listArtifacts(task)` and
`buildContentRequest(ctx)` together. Artifacts are projected on explicit reads
from persisted `Task.Data`; they are never stored as a second source of truth.
The list contains only stable `key`, `type`, and optional `mimeType` fields.
The content hook receives the selected key, raw decoded task data, the explicit
private upstream task id, the producer plugin version, channel authentication,
and a safe client Range/conditional-header subset. Its URL and headers exist
only for that proxy request.
When a Responses observation reaches persisted `SUCCESS`, the host also runs
the pinned plugin's `listArtifacts` and injects a read-only
`ctx.artifacts[key] = {key, type, mimeType?, url}` map into `renderEvents` or
`renderFinal`. Each `url` is a long-lived host-signed capability URL, never the
provider URL from `Task.Data`. Nonterminal and failed tasks receive no artifact
map. Capability construction or rendering failure fails only that Responses
observation; it cannot change the task, billing settlement, or refunds.
The absolute URL uses `TaskPublicAddress`, falling back only to
`ServerAddress`; multi-node deployments must share the effective
`CRYPTO_SECRET`.
Dashboard artifact reads return each `content_url` (or the legacy
`legacy_content_url`) directly, without a temporary URL exchange. Capability
generation and verification are stateless and have no expiry; after
verification the host still loads the task, owner, and plugin needed to serve
the artifact. Rotating `CRYPTO_SECRET` invalidates issued URLs. The `access`
query is redacted before request logging.
Deployment boundaries and concurrency environment variables are documented in
[v1.md](./v1.md#generic-task-management-api).
The host treats `protocols.openai_video.render` as a standard DTO, not an arbitrary
JSON passthrough. Unknown top-level fields and legacy `task_id` are removed,
`id` is forced to the public task id, and case-insensitive `url` entries are
removed from metadata. Provider output URLs belong only behind artifact
capabilities.
Provider-authenticated content URLs must use the channel base host or a
plugin-declared `meta.allowedHosts` entry. A public dynamic CDN URL may instead
set `credentialless: true`; the host then permits only GET/HEAD with no
plugin-supplied headers or body and applies SSRF checks to the initial URL and
every redirect.
Registry publication is generation-atomic. A request pins one plugin generation
for its full lifetime, while background polling may use a later active plugin
version. New versions must continue parsing responses for in-flight tasks.
Root administrators can inspect the local node with
`GET /api/plugin/task/runtime/status`. The response includes the node-local generation,
a deterministic revision of the active database overrides, the latest rebuild
outcome, and plugin-level compile or routing errors. Generation numbers are
local to a node; compare database revisions when diagnosing rollout lag between
nodes. If the database snapshot is temporarily unavailable, the endpoint keeps
serving node-local state and the last known revision with `database_error` set.
For live diagnosis, start the process with `DEBUG=true` and filter logs on
`task_plugin`. Plugin registry, routing, endpoint ownership, channel selection,
submit durability, polling adapters, and protocol observation emit safe
key/value lifecycle events. Request-context events carry the request id;
scheduled, background, and context-less work is labeled `SYSTEM`. Plugin
`console.log` output is also forwarded in DEBUG mode. Hook-time output is
prefixed with plugin key/version; module-initialization output may have an empty
identity during initial upload validation. Do not print credentials, headers,
request bodies, upstream payloads, or private URLs from plugin code; free-form
console output cannot be redacted by the host.
## Fixtures and dry runs
A fixture case is `{name?, hook, member?, args, expected?, expectedError?}`.
Keep deterministic cases for every exported hook, its main error branch, batch
behavior, renderers, usage, and content requests. Run a fixture locally with:
```sh
new-api plugin lint plugin.js
new-api plugin test plugin.js --fixture golden.json
```
Root administrators can open the plugin detail Sandbox tab, choose a hook, and
submit an `args` JSON array. `POST /api/plugin/task/:key/dryrun` compiles the
active database source or factory source in a temporary registry and invokes
only that synchronous function. Dry runs never execute a request descriptor and
therefore never contact an upstream service.
## Upload and release
Upload from the root-only task plugin page or `POST /api/plugin/task` with
`{"source":"...","remark":"..."}`. The server compiles the module, validates
v1 metadata and required exports, and rejects invalid source before saving it.
Use semantic plugin versions. Reusing a key/version with different source is
rejected; activate or roll back a stored version through the management page.
For a third-party platform, create a channel of type `Task Plugin`, select the
plugin key, provide an explicit base URL, and configure models. Clients may use
the plugin's declared native routes. The generic management surface remains
`POST /v1/tasks/:pluginKey`, `GET /v1/tasks/:taskId`, and
`GET /v1/tasks/:taskId/artifacts` plus
`GET|HEAD /v1/tasks/:taskId/artifacts/:key/content`.
## Security boundary
Plugins have no `fetch`, filesystem, `require`, imports, async functions, or
environment access. The host limits execution time, concurrency, input size,
allowed request hosts, and resolves OAuth credentials outside JavaScript.
Multipart files enter JavaScript only as opaque references.
This is not a hard memory-isolation boundary. A plugin sees data needed for the
current request and can influence an authenticated upstream request. Uploading a
plugin is an administrator-level trust decision equivalent to configuring a
channel credential. Review source and version diffs before activation. Never run
untrusted plugins merely because they compile.
Usage hooks may return facts such as seconds, resolution, or upstream units, but
must never calculate prices or attempt quota settlement. The host owns all
pricing and clamps billing conversions.
+51
View File
@@ -0,0 +1,51 @@
export type JSONValue = null | boolean | number | string | readonly JSONValue[] | {readonly [key: string]: JSONValue};
export type FileReference = Readonly<{ref: string; field: string; filename: string; mimeType: string; size: number}>;
export type FilePlaceholder = Readonly<{__fileRef: string; encoding: "base64" | "dataUrl"; mimeType?: string; maxBytes?: number}>;
export type DecodedBody =
| Readonly<{kind: "json"; value: JSONValue}>
| Readonly<{kind: "form"; fields: Readonly<Record<string, readonly string[]>>}>
| Readonly<{kind: "multipart"; fields: Readonly<Record<string, readonly string[]>>; files: readonly FileReference[]}>
| Readonly<{kind: "none"}>;
export interface NativeDecodeContext {method: string; path: string; params: Readonly<Record<string, string>>; query: Readonly<Record<string, readonly string[]>>; body: DecodedBody}
export interface ProtocolDecodeContext extends NativeDecodeContext {protocol: "openai_responses" | "openai_video"; operation: string; model: string; stream: boolean}
export type SubmitIntent = {kind: "submit"; model: string; action?: string; requestBody?: unknown; originTaskIds?: readonly string[]};
export type QueryIntent = {kind: "query"; taskIds: readonly string[]};
export type TaskIntent = SubmitIntent | QueryIntent;
export interface NativeRoute {method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; path: string; type: "submit" | "query" | "dynamic"; action?: string; taskIdParam?: string; decode?: string; render: string; models?: readonly string[]}
export type ProtocolName = "openai_responses" | "openai_video";
export type ResponsesMode = "stream" | "sync" | "background";
export type ProtocolClaim =
| "openai_video"
| {name: "openai_responses"; supports: readonly ResponsesMode[]; models?: readonly string[]}
| {name: "openai_video"; models?: readonly string[]};
export type LocalizedText = string | ({ en: string } & Record<string, string>);
export type UsageFieldSchema = {type: "number"; unit: "second" | "count" | "token" | "credit"; description?: LocalizedText} | {type: "boolean"; description?: LocalizedText} | {enum: readonly string[]; description?: LocalizedText; enumLabels?: Readonly<Record<string, LocalizedText>>};
export type UsageExample = {label: string; facts: Readonly<Record<string, string | number>>};
export interface Meta {sortPriority?: number; website?: string; apiVersion: 1; key: string; name: string; icon?: string; description?: LocalizedText; version: string; author: {name: string; url?: string}; baseUrl?: string; channelTypes?: readonly number[]; models: readonly string[]; fetchMode: "per_task" | "batch"; allowedHosts?: readonly string[]; routes?: readonly NativeRoute[]; protocols?: readonly ProtocolClaim[]; usageSchema?: Readonly<Record<string, UsageFieldSchema>>; usageExamples?: readonly UsageExample[]; auth?: "none" | "api_key" | "vertex_oauth" | {type: "none" | "api_key" | "oauth2_jwt"}}
export interface TaskView {task_id: string; status: string; progress?: string; fail_reason?: string; created_at?: number; updated_at?: number; data?: unknown; properties?: Record<string, unknown>}
export interface DriverContext {requestBody: unknown; requestHeaders: Readonly<Record<string, string>>; action: string; model: string; upstreamModel: string; baseUrl: string; apiKey?: string; authHeader: string; files: readonly FileReference[]; publicTaskId: string; originTasks?: readonly {taskId: string; upstreamTaskId: string; action: string; status: string; data: unknown}[]}
export interface TaskQueryContext {taskId: string; publicTaskId: string; action: string; model: string; upstreamModel: string; baseUrl: string; apiKey?: string; authHeader: string; auth?: unknown; data: unknown; state: unknown}
export interface BatchQueryContext {baseUrl: string; apiKey?: string; authHeader: string; auth?: unknown; tasks: readonly TaskQueryContext[]}
export type HookHTTPResponse = {readonly status: number; readonly headers: Readonly<Record<string, string>>}
export interface RequestDescriptor {url: string; method?: string; headers?: Record<string, string>; /** JSON body may contain FilePlaceholder objects at any depth; the host replaces each with a Base64 or data-URL string. */ body?: unknown; credentialless?: boolean; action?: string; model?: string; rewriteModel?: string; bodyType?: "json" | "multipart"; parts?: readonly {name: string; value?: unknown; fileRef?: string; filename?: string}[]}
export interface UpstreamResponse {statusCode: number; headers: Readonly<Record<string, readonly string[]>>; body: unknown}
export interface NormalizedTaskResult {taskId?: string; status: "NOT_START" | "SUBMITTED" | "QUEUED" | "IN_PROGRESS" | "SUCCESS" | "FAILURE" | "UNKNOWN"; progress?: string; reason?: string; url?: string; remoteUrl?: string; completionTokens?: number; totalTokens?: number}
export interface TaskArtifact {key: string; type: "video" | "audio" | "image" | "file"; mimeType?: string}
export declare const meta: Meta;
export declare const native: Record<string, ((ctx: NativeDecodeContext) => TaskIntent) | ((ctx: NativeDecodeContext, task: TaskView | readonly TaskView[]) => unknown)> & {error?: (ctx: NativeDecodeContext, error: {code: string; message: string; httpStatus: number; retryable: boolean}) => unknown};
export declare const protocols: {
openai_responses?: {decodeRequest(ctx: ProtocolDecodeContext): SubmitIntent; renderEvents?(ctx: unknown, task: TaskView, previousState: unknown): unknown; renderFinal?(ctx: unknown, task: TaskView): unknown};
openai_video?: {decodeRequest(ctx: ProtocolDecodeContext): SubmitIntent; render(ctx: unknown, task: TaskView): unknown};
};
export declare function buildSubmitRequest(ctx: DriverContext): RequestDescriptor;
export declare function parseSubmitResponse(ctx: DriverContext, response: UpstreamResponse): {taskId: string; taskData?: unknown; immediate?: NormalizedTaskResult; state?: unknown};
export declare function buildQueryRequest(ctx: TaskQueryContext): RequestDescriptor;
export declare function buildBatchQueryRequest(ctx: BatchQueryContext, tasks: readonly TaskQueryContext[]): RequestDescriptor;
export declare function parseTaskResult(ctx: TaskQueryContext, body: unknown, response: HookHTTPResponse): NormalizedTaskResult;
export declare function parseBatchResult(ctx: BatchQueryContext, body: unknown, response: HookHTTPResponse): readonly (NormalizedTaskResult & {taskId: string; data?: unknown; state?: unknown})[];
export declare function extractUsage(ctx: DriverContext & {usagePurpose?: "facts" | "billing_ratios"}): Readonly<Record<string, string | number | boolean>> | null;
export declare function extractUsageOnSubmit(ctx: DriverContext, taskData: unknown): Readonly<Record<string, string | number | boolean>> | null;
export declare function extractUsageOnComplete(task: TaskView, result: NormalizedTaskResult, data: unknown): Readonly<Record<string, string | number | boolean>> | null;
export declare function listArtifacts(task: {taskId: string; status: string; action: string; data: unknown; producerVersion: string}): readonly TaskArtifact[];
export declare function buildContentRequest(ctx: DriverContext & {artifactKey: string; data: unknown; state?: unknown; upstreamTaskId: string; clientRequest: {method: "GET" | "HEAD"; headers: Readonly<Record<string, string>>}}): RequestDescriptor;
+226
View File
@@ -0,0 +1,226 @@
# Task Plugin API v1
Task Plugin v1 has two independent entry surfaces. `meta.routes` registers plugin-owned native URLs; `meta.protocols` claims host-owned protocols without registering or copying their URLs. `apiVersion` remains `1`.
## Manifest
```js
export const meta = {
apiVersion: 1,
key: "vendor",
name: "Vendor",
version: "1.0.0",
author: {name: "Author"},
description: {en: "Video generation via the vendor API", zh: "通过厂商接口生成视频"},
models: ["vendor-model"],
fetchMode: "per_task",
routes: [
{method: "POST", path: "/vendor/v1/jobs", type: "submit", decode: "createJob", render: "jobCreated"},
{method: "GET", path: "/vendor/v1/jobs/:task_id", type: "query", render: "jobStatus"},
],
protocols: [{name: "openai_responses", supports: ["stream", "sync", "background"]}, "openai_video"],
};
```
`submit` and `dynamic` routes require `decode` and `render`. `query` routes require `render`, prohibit `decode`, and use `taskIdParam` (default `task_id`). Names refer only to callable members of `native`. Route conflicts use method plus normalized path shape and the route index is published atomically with its plugin generation.
`routes[].models` optionally restricts a `submit` or `dynamic` route to a subset of `meta.models`. The host matches the canonical top-level `model` body field before any JS hook runs; a missing, non-string, or unlisted model is rejected with 400 (plugin routes are exclusive paths, so there is no relay fallback). Declare it only when the model lives at the body top level — vendor formats that nest the model elsewhere should omit `models` and filter inside `decode`. `query` routes have no body and reject the field.
Each `protocols` entry claims a host protocol. A protocol that defines modes must be claimed in object form with an explicit `supports`; the bare-string form is valid only for protocols without modes (`openai_video`). `supports` declares which client request forms the plugin accepts on `openai_responses`: `"stream"` (`stream: true`), `"sync"` (neither flag; the call blocks until the terminal Response), `"background"` (`background: true`; the create returns a pending Response immediately). An unsupported request form is rejected with a 400 at channel selection, before any plugin hook runs and before billing. Retrieval (`GET /v1/responses/:response_id`) is not a mode: every created response is always retrievable.
`{name, models}` still narrows that protocol's endpoint bindings to a subset of `meta.models` and composes with `supports`. Unlisted models never enter the plugin on that protocol path — they fall through to the built-in Go relay. Cross-plugin endpoint conflicts are judged on the narrowed set, so two plugins may share one protocol path by claiming disjoint model sets.
Enabled uploads pre-flight the candidate against the live routing generation and reject the first channel-type, native-route, or protocol-model conflict (the error names the counterpart plugin). Set `force: true` or `enabled: false` to store the plugin anyway.
`endpoints`, `routes[].renderer`, global `resolveRequest`, global `renderError`, and global `renderers` are rejected. `parseSubmitResponse` returns only `{taskId, taskData, immediate?, state?}`; `clientResponse` is rejected.
`icon` is an optional LobeHub icon name string (for example `Sora.Color`). The values `text` and `text:<label>` request a generated text avatar instead (label defaults to the first two characters of `name`). Inline data URIs and remote URLs are rejected. `icon` is display-only and does not participate in routing or billing.
A plugin whose vendor has no LobeHub icon ships its logo as a sidecar file, `icon.svg` or `icon.png`, next to `plugin.js`; the manifest stays readable and the image never appears in source diffs. Built-in plugins embed the sidecar from `plugins/tasks/<key>/`. For uploads the admin UI reads the file and sends it in the upload request's separate `icon` field as `data:image/png;base64,...` or `data:image/svg+xml;base64,...`; marketplace indexes declare it as `iconFile.path` and the installer fetches it from the index's own origin. The gateway stores the logo apart from the source and serves it from `GET /api/plugin/task/:key/icon` (optionally `?version=`), which the UI renders only through `<img>`. Limits: 512 KiB; PNG must carry the PNG signature; SVG must be well-formed XML with an `svg` root and no `script` or `foreignObject` elements, event-handler attributes, DOCTYPE, `javascript:` values, or absolute `http(s)` references. Supply artwork legible on both light and dark backgrounds (an SVG may use a `prefers-color-scheme` media query).
`baseUrl` is an optional default upstream address for channels of type 61 ("Task Plugin"). When an administrator binds the plugin and leaves the channel Base URL empty, the host copies this value onto the channel before validation and records `base_url_source: plugin_default` in the channel audit event; the persisted channel value is what every later request uses, so a later plugin update that changes `baseUrl` does not move existing channels. The value must be an absolute `http` or `https` URL without credentials, query, or fragment; the host is lowercased and must be ASCII (use punycode), trailing slashes are stripped, and the normalized value must not exceed 191 characters (the width of the channel `base_url` column on MySQL). Private, loopback, and plain `http` addresses are allowed for self-hosted upstreams; the admin UI flags them before binding. `baseUrl` is ignored on legacy `channelTypes` channels, which keep the built-in per-type default. The default host is not implicitly added to `allowedHosts`: if the administrator points the channel at a different host, plugin requests to the author's default host are rejected.
`allowedHosts` lists extra hosts that plugin requests may target besides the channel base URL host. Each entry is `host` or `host:port` (IPv6 literals bracketed, for example `[::1]:8080`); schemes, paths, credentials, and queries are rejected. Entries are lowercased and must be unique after normalization. An entry with a port matches only requests to that port (default ports `80`/`443` match requests that omit them).
| Field | Type | Notes |
|-------|------|-------|
| `key` | string | Required. Canonical plugin id, ≤ 30 characters. |
| `name` | string | Required. Display name. |
| `icon` | string | Optional LobeHub icon or `text` / `text:<label>`. ≤ 128 characters. Image logos ship as a sidecar `icon.svg` / `icon.png`, not in the manifest. |
| `description` | LocalizedText | Optional plugin summary. See LocalizedText. ≤ 512 runes per locale. |
| `version` | string | Required semver. |
| `sortPriority` | `number` | Optional signed 32-bit integer, default `0`. Higher values appear first; ties use ascending plugin key. Display only: does not change routing or override precedence. |
| `website` | `string` | Optional plugin website, independent of `author.url` and `baseUrl`. Empty is allowed; otherwise an absolute HTTPS URL with a valid ASCII hostname (use punycode for internationalized domains), without credentials, whitespace, control characters, or backslashes. Paths, queries, and fragments are allowed. |
| `author` | `{name, url?}` | Required name; `url`, when present, must be an absolute HTTP(S) URL. |
| `baseUrl` | string | Optional default Base URL for type-61 Task Plugin channels. Absolute `http(s)` URL, no credentials/query/fragment, ASCII host, trailing slash stripped, ≤ 191 characters. |
| `allowedHosts` | string[] | Optional extra request hosts, `host` or `host:port`. No schemes, paths, or credentials. |
`channelTypes` lists the legacy channel types this plugin's driver can drive (for example, sora declares `[55, 1]` because the same OpenAI-type base URL and bearer key serve both chat and video). Every entry equally participates in channel selection, historical `Task.Platform` matching, and the `byChannelType` routing index; the same type value may not appear on two plugins. Third-party plugins normally omit `channelTypes` and live on type-61 "Task Plugin" channels bound by `task_plugin_key`. The previous split identity/compatibility field names are rejected.
Numeric `usageSchema` fields declare a host-owned unit of `second`, `count`, `token`, or `credit`. Boolean fields declare `{type: "boolean"}`.
`meta.usageExamples` is an optional display-only list of pricing examples. Each `label` is a human-readable spec name. Each `facts` object must be a complete vector over `usageSchema`: every declared key present, no undeclared keys, and values that pass the same validation as usage facts. Plugins whose schema contains a `unit: "token"` number field must declare at least one example. Examples never affect billing. Labels are spec names such as `std · 10s`, not storefront prices.
## LocalizedText
`meta.description` and each `usageSchema` field `description` accept LocalizedText. A bare string is equivalent to `{en: <string>}`. A map must include a non-empty `en` value. The host normalizes both forms to a map; API responses always emit an object.
Enum fields may additionally declare `enumLabels`, a map from enum values to LocalizedText display names:
```js
video_input: {
enum: ["none", "video"],
description: {en: "Reference video input", zh: "参考视频输入"},
enumLabels: {
none: {en: "No reference video", zh: "无参考视频"},
video: {en: "With reference video", zh: "有参考视频"},
},
}
```
`enumLabels` is optional and may cover only some options. It is valid only on enum fields, and every key must exactly match a declared enum value. Each label follows the same locale normalization, required English fallback and 256-rune-per-locale limit as a field description. Labels are short phrases without trailing punctuation. They never change usage facts, expression conditions or values submitted by UI controls. Boolean fields use their description with a localized yes/no state and cannot declare `enumLabels`.
The UI selects the current language, then its primary language, then English. If no option label is provided, it displays the original enum value; a missing field description falls back to the field name. Labels are plugin data, not frontend translation keys.
This is an additive extension of `apiVersion: 1`. Updated gateways continue to load plugins without `enumLabels`; older gateways with strict property validation reject plugins that declare it.
```js
description: "Video generation via the vendor API"
description: {en: "Video generation via the vendor API", zh: "通过厂商接口生成视频"}
```
Rules:
- Locale keys match `^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$`. A map has at most 16 locales.
- Keys are canonicalized to BCP-47 casing (`EN` → `en`, `zh-tw` → `zh-TW`, `zh-hans` → `zh-Hans`); two keys that collide after canonicalization are rejected as duplicates.
- Each value is trimmed, must be non-empty, and must not contain control characters.
- `meta.description` is at most 512 runes per locale. Each `usageSchema` field `description` is at most 256 runes per locale.
- The frontend resolves a locale with exact tag → primary subtag → `en` (for example `zh-TW` → `zh` → `en`).
- Description copy in any language must not include vendor currency prices. The same prohibition applies to `usageExamples` labels.
### Description writing and translation conventions
Descriptions are short, user-facing phrases without trailing punctuation in any language. Keep translations equivalent in meaning. For `usageSchema` fields, use the following wording:
| Field meaning | Wording | English example | Chinese example |
| --- | --- | --- | --- |
| Numeric billing quantity | Billing subject + unit price | Song generation unit price | 生成歌曲单价 |
| Action | Action phrase | Generate songs | 生成歌曲 |
| Boolean | Whether a state is enabled or something is present | Whether audio is generated / Reference video present | 是否生成音频 / 存在参考视频 |
| Other enum condition | Short condition name | Output video resolution | 输出视频分辨率 |
- A shared description must cover all supported actions. For Suno, `clips.description` is `Song or lyrics generation unit price` / `生成歌曲或歌词单价`, and `action.description` is `Generate songs or lyrics` / `生成歌曲或歌词`.
- Boolean descriptions name the affirmative state represented by `true`; do not add a question mark or invert the meaning of the field.
- Preserve distinctions such as input versus output. Units belong in `unit`; do not repeat unit explanations or numeric prices in the description.
- Keep protocol limits, usage sources, estimation and settlement details in code comments or technical documentation, rather than in display descriptions.
- For example, replace `Requested video duration in seconds.` / `请求的视频时长,单位为秒。` with `Video generation unit price` / `视频生成单价`; replace `Whether audio is generated. Default true.` / `是否生成音频。默认为 true。` with `Whether audio is generated` / `是否生成音频`.
- These are authoring conventions for plugin metadata, not additional runtime validation rules. Existing `LocalizedText` wire types, usage quantities, enum values and billing semantics are unchanged; a numeric field still contains usage, not a price.
## Request body
Every decoder receives one host-parsed body:
```ts
{kind: "json", value}
{kind: "form", fields: Record<string, readonly string[]>}
{kind: "multipart", fields: Record<string, readonly string[]>, files: readonly FileReference[]}
{kind: "none"}
```
`FileReference` contains `{ref, field, filename, mimeType, size}`. The ref is opaque and request-scoped; file bytes, readers, temporary paths, and a second inbound `requestBody` representation are never exposed to JavaScript. The decoder creates a new canonical `{kind:"submit", model, action?, requestBody?, originTaskIds?}` intent. Driver hooks consume that normalized `requestBody` and remain surface-independent.
## Request building
`buildSubmitRequest` returns a request descriptor. JSON bodies (the default) may embed a file placeholder at any object or array depth. The host replaces each placeholder with an encoded string before the request is sent; JavaScript never sees file bytes.
```js
{ __fileRef: "request_file:input_reference", encoding: "base64" } // raw Base64
{ __fileRef: "request_file:input_reference", encoding: "dataUrl" } // data:<mime>;base64,<data>
{ __fileRef: "request_file:input_reference", encoding: "dataUrl", mimeType: "image/png", maxBytes: 20971520 }
```
Placeholder objects may contain only `__fileRef`, `encoding`, `mimeType`, and `maxBytes`. `encoding` is `base64` or `dataUrl`. `__fileRef` must resolve to an uploaded multipart file (`request_file:<field>`). `mimeType` optionally overrides the part `Content-Type` for `dataUrl` (otherwise the part header, else `application/octet-stream`). `maxBytes` is an optional vendor cap; the host also applies `MAX_FILE_DOWNLOAD_MB` (default 64MB) to each file and to the total inlined bytes. Multipart descriptors still stream files with `parts[].fileRef`.
## Origin tasks
Decoders normalize vendor references (draft ids, continuation ids, gateway-issued asset references) into `originTaskIds` as public gateway task IDs. The host never parses vendor request bodies.
The host verifies ownership, requires every referenced task to belong to the same plugin platform set and one enabled channel, pins that channel for the submit (including retries), and injects the resolved rows into driver hooks as `ctx.originTasks` (`taskId`, `upstreamTaskId`, `action`, `status`, and `data`, including the private upstream id). Renderers and presenters never receive `originTasks`.
## Native surface
```js
export const native = {
createJob(ctx) { return {kind: "submit", model: ctx.body.value.model, requestBody: ctx.body.value}; },
jobCreated(ctx, task) { return {job_id: task.task_id, state: "queued"}; },
jobStatus(ctx, task) { return task.data; },
error(ctx, error) { return {code: error.code, message: error.message}; },
};
```
The host authenticates, checks ownership, persists the task, builds `TaskView`, and replaces known private task-id fields with the public ID before calling presenters. Native presenters own the vendor envelope. Authenticated native query presenters may intentionally pass through provider URLs from `task.data`.
## Error contract
Hook `Error` messages are surfaced (truncated and sanitized) to API callers. Plugin authors should write validation failures as user-readable sentences.
## Host protocols
The host registry owns these bindings:
| Protocol | Operation | Binding | Body |
|--------------------|-----------|---------------------------|-----------------------------------|
| `openai_responses` | create | `POST /v1/responses` | JSON |
| `openai_responses` | retrieve | `GET /v1/responses/:response_id` | none |
| `openai_video` | create | `POST /v1/videos` | JSON or multipart |
| `openai_video` | retrieve | `GET /v1/videos/:task_id` | none |
| `openai_video` | content | `GET | HEAD /v1/videos/:task_id/content` | none |
`protocols.openai_responses` always requires `decodeRequest`. The remaining hooks are derived from `supports` and verified exactly at load in both directions: `"stream"` requires `renderEvents`; `"sync"` or `"background"` requires `renderFinal`. A hook required by a supported mode but not exported, or an exported hook that no supported mode uses, rejects the plugin. For a plugin supporting only `"stream"`, the host renders retrieval at terminal SUCCESS by calling `renderEvents` once with no previous state — the same first-call-at-terminal semantics streaming already requires. The mode vocabulary is append-only under apiVersion 1: new modes may be added (existing manifests are unaffected; a manifest declaring a mode loads only on gateways that define it), but a mode may never be removed, renamed, or re-mapped to different hooks without an apiVersion bump. `protocols.openai_video` requires `decodeRequest` for create and `render` for retrieve. The host extracts and pins `ctx.model`, normalizes `ctx.stream`, frames SSE, creates failure envelopes, and calls `renderFinal` only for `SUCCESS`.
`background: true` on create returns the pending Response immediately (host-synthesized, no plugin hook). Retrieval renders via `renderFinal` only at terminal status. Plugins declaring `"background"` need no create-time hook; the deliverable is rendered at retrieval via `renderFinal`. Plugins cannot observe the `background` field. Unlike the upstream OpenAI API, a plugin that does not declare `"stream"` rejects `stream: true` with a 400; the error names the supported forms.
Protocol media uses host-injected `ctx.artifacts[key].url`. Provider URLs from `task.data` are not protocol output. OpenAI Video projections are DTO-whitelisted and the host overwrites identity, lifecycle, timestamps, and removes URL-like metadata.
## Persisted data and driver hooks
The persisted field remains `task.data`; there is no `task.raw` alias. Driver hooks (`buildSubmitRequest`, `parseSubmitResponse`, query/result, usage, artifact, and content hooks) stay flat and must not branch on the client path or protocol.
`ctx.model` is the billing and display identity (the origin name the client sent, including a channel-mapping alias). `ctx.upstreamModel` is the machine identity after channel `model_mapping`. Rate tables and model-keyed usage facts must use `ctx.upstreamModel || ctx.model`. Decode and render hooks that echo the client model must keep `ctx.model`. `buildSubmitRequest` must not set descriptor top-level `model` on a mapped pin; the host requires the plugin to echo the alias verbatim. Background polling has no relay info, so query hooks receive both identities from the persisted task properties, and `ctx.upstreamModel` falls back to `ctx.model` when the task was submitted without a channel mapping.
## Polling contract
Query and parse hooks use `TaskQueryContext`, not `DriverContext`. The host rebuilds that context from the persisted task row. There is no query-side `requestBody`.
| Field | Source |
|-------|--------|
| `taskId` | Upstream task id (`PrivateData.UpstreamTaskID`, else `TaskID`) |
| `publicTaskId` | Gateway task id |
| `action` | Normalized persisted action |
| `model` | `Properties.OriginModelName` |
| `upstreamModel` | `Properties.UpstreamModelName`, falling back to `model` |
| `baseUrl` / `apiKey` / `authHeader` / `auth` | Channel credentials |
| `data` | Current `Task.Data` snapshot |
| `state` | Plugin-owned `PrivateData.PluginState` |
`Task.Data` is the latest upstream response snapshot for presenters and artifacts. The host overwrites it on every successful parse. Values that must survive across poll rounds belong in `state`.
`parseSubmitResponse`, `parseTaskResult`, and each `parseBatchResult` item may return optional `state`. The host writes it only when the hook returns it. Omitting `state` preserves the previous value. Oversized state is rejected with a warning, not truncated.
`buildBatchQueryRequest(ctx, tasks)` and `parseBatchResult` receive `tasks: TaskQueryContext[]`. `parseTaskResult` / `parseBatchResult` also receive `{status, headers}` for the upstream HTTP response.
`status: "UNKNOWN"` means the plugin does not recognize the response. Do not write `|| "IN_PROGRESS"` (or equivalent) for a missing table entry. The host treats `UNKNOWN`, hook errors, empty status, and unrecognized status strings as consecutive poll failures.
The host classifies the HTTP status before trusting a non-terminal parse:
| Upstream HTTP | Host action |
|---------------|-------------|
| 2xx | Call the parse hook |
| 404 / 410 | Immediate `FAILURE` and refund |
| 401 / 403 | Leave task status unchanged; increment `PollFailures`; `LogWarn` with channel id. Channels are not auto-disabled. |
| 429 / 5xx / transport error | Increment `PollFailures` |
| Other 4xx | Call the parse hook with `response.status`. A still-non-terminal result is unrecognized and increments `PollFailures`. |
A valid 2xx non-terminal parse resets `PollFailures` to 0. After `TASK_POLL_MAX_FAILURES` (default 20) consecutive failures the task becomes `FAILURE` and follows the existing refund chain. The 24h `TASK_TIMEOUT_MINUTES` sweep remains the outer deadline.
Marketplace index v1 plugin entries also accept optional `sortPriority` and `website` fields. Each source is sorted independently by descending priority and ascending key. Missing or invalid priorities fall back to `0`; invalid websites are hidden. Installed plugins and channel binding options use plugin metadata. Website links open only on user interaction; the host does not fetch them.
+114
View File
@@ -0,0 +1,114 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/QuantumNous/new-api/docs/plugin-api/v1.schema.json",
"title": "new-api Task Plugin v1 manifest",
"type": "object",
"additionalProperties": false,
"required": ["apiVersion", "key", "name", "version", "author", "models", "fetchMode"],
"properties": {
"sortPriority": {"type": "integer", "minimum": -2147483648, "maximum": 2147483647, "default": 0},
"website": {"oneOf": [{"const": ""}, {"type": "string", "format": "uri", "pattern": "^[Hh][Tt][Tt][Pp][Ss]://[^/@?#\\s]+(?:[/?#]|$)"}], "description": "Absolute HTTPS URL with a valid ASCII hostname (use punycode for internationalized domains) and no credentials; validated by the host."},
"apiVersion": {"const": 1}, "key": {"type": "string"}, "name": {"type": "string"}, "icon": {"type": "string", "maxLength": 128}, "description": {"$ref": "#/$defs/localizedText"}, "version": {"type": "string"},
"author": {"type": "object", "required": ["name"], "additionalProperties": false, "properties": {"name": {"type": "string"}, "url": {"type": "string", "format": "uri"}}},
"baseUrl": {"type": "string", "format": "uri", "pattern": "^https?://", "maxLength": 191},
"channelTypes": {"type": "array", "uniqueItems": true, "items": {"type": "integer", "minimum": 1}}, "models": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
"fetchMode": {"enum": ["per_task", "batch"]}, "allowedHosts": {"type": "array", "uniqueItems": true, "items": {"type": "string"}},
"protocols": {"type": "array", "uniqueItems": true, "items": {"oneOf": [
{"enum": ["openai_video"]},
{"type": "object", "additionalProperties": false, "required": ["name", "supports"], "properties": {"name": {"const": "openai_responses"}, "supports": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"enum": ["stream", "sync", "background"]}}, "models": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}}}},
{"type": "object", "additionalProperties": false, "required": ["name"], "properties": {"name": {"const": "openai_video"}, "models": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}}}}
]}},
"routes": {"type": "array", "items": {"$ref": "#/$defs/route"}}, "usageSchema": {"type": "object", "additionalProperties": {"$ref": "#/$defs/usageField"}}, "usageExamples": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["label", "facts"], "properties": {"label": {"type": "string"}, "facts": {"type": "object"}}}}, "auth": {}
},
"$defs": {
"usageField": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": {"enum": ["number", "boolean"]},
"unit": {"enum": ["second", "count", "token", "credit"]},
"enum": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string"}},
"description": {"$ref": "#/$defs/usageLabel"},
"enumLabels": {
"type": "object",
"additionalProperties": {"$ref": "#/$defs/usageLabel"},
"description": "Optional display names keyed by declared enum values; the host validates membership. Missing entries display the original value."
}
},
"oneOf": [
{"required": ["enum"], "not": {"anyOf": [{"required": ["type"]}, {"required": ["unit"]}]}},
{"required": ["type", "unit"], "properties": {"type": {"const": "number"}}, "not": {"anyOf": [{"required": ["enum"]}, {"required": ["enumLabels"]}]}},
{"required": ["type"], "properties": {"type": {"const": "boolean"}}, "not": {"anyOf": [{"required": ["unit"]}, {"required": ["enum"]}, {"required": ["enumLabels"]}]}}
]
},
"usageLabel": {
"allOf": [
{"$ref": "#/$defs/localizedText"},
{"if": {"type": "string"}, "then": {"maxLength": 256}, "else": {"additionalProperties": {"maxLength": 256}}}
]
},
"localizedText": {
"oneOf": [
{"type": "string", "minLength": 1},
{
"type": "object",
"required": ["en"],
"maxProperties": 16,
"propertyNames": {"pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$"},
"additionalProperties": {"type": "string", "minLength": 1}
}
]
},
"filePlaceholder": {
"type": "object",
"additionalProperties": false,
"required": ["__fileRef", "encoding"],
"properties": {
"__fileRef": {"type": "string", "minLength": 1},
"encoding": {"enum": ["base64", "dataUrl"]},
"mimeType": {"type": "string", "minLength": 1},
"maxBytes": {"type": "integer", "exclusiveMinimum": 0}
}
},
"route": {"type": "object", "additionalProperties": false, "required": ["method", "path", "type", "render"], "properties": {"method": {"enum": ["GET", "POST", "PUT", "PATCH", "DELETE"]}, "path": {"type": "string", "pattern": "^/"}, "type": {"enum": ["submit", "query", "dynamic"]}, "action": {"type": "string"}, "taskIdParam": {"type": "string"}, "decode": {"type": "string"}, "render": {"type": "string"}, "models": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}}}, "allOf": [{"if": {"properties": {"type": {"const": "query"}}}, "then": {"allOf": [{"not": {"required": ["decode"]}}, {"not": {"required": ["models"]}}]}}, {"if": {"properties": {"type": {"enum": ["submit", "dynamic"]}}}, "then": {"required": ["decode"]}}]},
"taskQueryContext": {
"type": "object",
"additionalProperties": false,
"required": ["taskId", "publicTaskId", "action", "model", "upstreamModel", "baseUrl", "authHeader", "data", "state"],
"properties": {
"taskId": {"type": "string"},
"publicTaskId": {"type": "string"},
"action": {"type": "string"},
"model": {"type": "string"},
"upstreamModel": {"type": "string"},
"baseUrl": {"type": "string"},
"apiKey": {"type": "string"},
"authHeader": {"type": "string"},
"auth": true,
"data": true,
"state": true
}
},
"batchQueryContext": {
"type": "object",
"additionalProperties": false,
"required": ["baseUrl", "authHeader", "tasks"],
"properties": {
"baseUrl": {"type": "string"},
"apiKey": {"type": "string"},
"authHeader": {"type": "string"},
"auth": true,
"tasks": {"type": "array", "items": {"$ref": "#/$defs/taskQueryContext"}}
}
},
"hookHTTPResponse": {
"type": "object",
"additionalProperties": false,
"required": ["status", "headers"],
"properties": {
"status": {"type": "integer"},
"headers": {"type": "object", "additionalProperties": {"type": "string"}}
}
}
}
}
+107
View File
@@ -0,0 +1,107 @@
# Glossaire Français (French Glossary)
Ce document fournit des traductions standards françaises pour la terminologie clé du projet afin d'assurer la cohérence et la précision des traductions.
This document provides standard French translations for key project terminology to ensure consistency and accuracy in translations.
## Concepts de Base (Core Concepts)
- L'utilisation d'émojis dans les traductions est autorisée s'ils sont présents dans l'original
- L'utilisation de termes purement techniques est autorisée s'ils sont présents dans l'original
- L'utilisation de termes techniques en anglais est autorisée s'ils sont largement utilisés dans l'environnement technique francophone (par exemple, API)
| Chinois | Français | Anglais | Description |
|---------|----------|---------|-------------|
| 倍率 | Ratio | Ratio/Multiplier | Multiplicateur utilisé pour le calcul des prix. **Important :** Dans le contexte des calculs de prix, toujours utiliser "Ratio" plutôt que "Multiplicateur" pour assurer la cohérence terminologique |
| 令牌 | Jeton | Token | Identifiants d'accès API ou unités de texte traitées par les modèles |
| 渠道 | Canal | Channel | Canal d'accès aux fournisseurs d'API |
| 分组 | Groupe | Group | Classification des utilisateurs ou des jetons |
| 额度 | Quota | Quota | Quota de services disponible pour l'utilisateur |
## Modèles (Model Related)
| Chinois | Français | Anglais | Description |
|---------|----------|---------|-------------|
| 提示 | Invite | Prompt | Contenu d'entrée du modèle |
| 补全 | Complétion | Completion | Contenu de sortie du modèle. **Important :** Ne pas utiliser "Achèvement" ou "Finalisation" - uniquement "Complétion" pour correspondre à la terminologie technique |
| 输入 | Entrée | Input/Prompt | Contenu envoyé au modèle |
| 输出 | Sortie | Output/Completion | Contenu retourné par le modèle |
| 模型倍率 | Ratio du modèle | Model Ratio | Ratio de tarification pour différents modèles |
| 补全倍率 | Ratio de complétion | Completion Ratio | Ratio de tarification supplémentaire pour la sortie |
| 固定价格 | Prix fixe | Price per call | Prix par appel |
| 按量计费 | Paiement à l'utilisation | Pay-as-you-go | Tarification basée sur l'utilisation |
| 按次计费 | Paiement par appel | Pay-per-view | Prix fixe par appel |
## Gestion des Utilisateurs (User Management)
| Chinois | Français | Anglais | Description |
|---------|----------|---------|-------------|
| 超级管理员 | Super-administrateur | Root User | Administrateur avec les privilèges les plus élevés |
| 管理员 | Administrateur | Admin User | Administrateur système |
| 普通用户 | Utilisateur normal | Normal User | Utilisateur avec privilèges standards |
## Recharge et Échange (Recharge & Redemption)
| Chinois | Français | Anglais | Description |
|---------|----------|---------|-------------|
| 充值 | Recharge | Top Up | Ajout de quota au compte |
| 兑换码 | Code d'échange | Redemption Code | Code qui peut être échangé contre du quota |
## Gestion des Canaux (Channel Management)
| Chinois | Français | Anglais | Description |
|---------|----------|---------|-------------|
| 渠道 | Canal | Channel | Canal du fournisseur d'API |
| API密钥 | Clé API | API Key | Clé d'accès API. **Important :** Utiliser "Clé API" au lieu de "Jeton API" pour plus de précision et conformément à la terminologie technique francophone établie. Le terme "Clé" reflète mieux la fonctionnalité d'accès aux ressources, tandis que "Jeton" est plus souvent associé aux unités de texte dans le contexte du traitement des modèles linguistiques. |
| 优先级 | Priorité | Priority | Priorité de sélection du canal |
| 权重 | Poids | Weight | Poids d'équilibrage de charge |
| 代理 | Proxy | Proxy | Adresse du serveur proxy |
| 模型重定向 | Redirection de modèle | Model Mapping | Remplacement du nom du modèle dans le corps de la requête |
| 供应商 | Fournisseur | Provider/Vendor | Fournisseur de services ou d'API |
## Sécurité (Security Related)
| Chinois | Français | Anglais | Description |
|---------|----------|---------|-------------|
| 两步验证 | Authentification à deux facteurs | Two-Factor Authentication | Méthode de vérification de sécurité supplémentaire pour les comptes |
| 2FA | 2FA | Two-Factor Authentication | Abréviation de l'authentification à deux facteurs |
## Recommandations de Traduction (Translation Guidelines)
### Variantes Contextuelles de Traduction
**Invite/Entrée (Prompt/Input)**
- **Invite** : Lors de l'interaction avec les LLM, dans l'interface utilisateur, lors de la description de l'interaction avec le modèle
- **Entrée** : Dans la tarification, la documentation technique, la description du processus de traitement des données
- **Règle** : S'il s'agit de l'expérience utilisateur et de l'interaction avec l'IA → "Invite", s'il s'agit du processus technique ou des calculs → "Entrée"
**Jeton (Token)**
- Jeton d'accès API (API Token)
- Unité de texte traitée par le modèle (Text Token)
- Jeton d'accès système (Access Token)
**Quota (Quota)**
- Quota de services disponible pour l'utilisateur
- Parfois traduit comme "Crédit"
### Particularités de la Langue Française
- **Formes plurielles** : Nécessite une implémentation correcte des formes plurielles (_one, _other)
- **Accords grammaticaux** : Attention aux accords grammaticaux dans les termes techniques
- **Genre grammatical** : Accord du genre des termes techniques (par exemple, "modèle" - masculin, "canal" - masculin)
### Termes Standardisés
- **Complétion (Completion)** : Contenu de sortie du modèle
- **Ratio (Ratio)** : Multiplicateur pour le calcul des prix
- **Code d'échange (Redemption Code)** : Utilisé au lieu de "Code d'échange" pour plus de précision
- **Fournisseur (Provider/Vendor)** : Organisation ou service fournissant des API ou des modèles d'IA
---
**Note pour les contributeurs :** Si vous trouvez des incohérences dans les traductions de terminologie ou si vous avez de meilleures suggestions de traduction pour le français, n'hésitez pas à créer une Issue ou une Pull Request.
**Contribution Note for French:** If you find any inconsistencies in terminology translations or have better translation suggestions for French, please feel free to submit an Issue or Pull Request.
+86
View File
@@ -0,0 +1,86 @@
# 翻译术语表 (Translation Glossary)
本文档为翻译贡献者提供项目中关键术语的标准翻译参考,以确保翻译的一致性和准确性。
This document provides standard translation references for key terminology in the project to ensure consistency and accuracy for translation contributors.
## 核心概念 (Core Concepts)
| 中文 | English | 说明 | Description |
|------|---------|------|-------------|
| 倍率 | Ratio | 用于计算价格的乘数因子 | Multiplier factor used for price calculation |
| 令牌 | Token | API访问凭证,也指模型处理的文本单元 | API access credentials or text units processed by models |
| 渠道 | Channel | API服务提供商的接入通道 | Access channel for API service providers |
| 分组 | Group | 用户或令牌的分类,影响价格倍率 | Classification of users or tokens, affecting price ratios |
| 额度 | Quota | 用户可用的服务额度 | Available service quota for users |
## 模型相关 (Model Related)
| 中文 | English | 说明 | Description |
|------|---------|------|-------------|
| 提示 | Prompt | 模型输入内容 | Model input content |
| 补全 | Completion | 模型输出内容 | Model output content |
| 输入 | Input/Prompt | 发送给模型的内容 | Content sent to the model |
| 输出 | Output/Completion | 模型返回的内容 | Content returned by the model |
| 模型倍率 | Model Ratio | 不同模型的计费倍率 | Billing ratio for different models |
| 补全倍率 | Completion Ratio | 输出内容的额外计费倍率 | Additional billing ratio for output content |
| 固定价格 | Price per call | 按次计费的价格 | Fixed price per call |
| 按量计费 | Pay-as-you-go | 根据使用量计费 | Billing based on usage |
| 按次计费 | Pay-per-view | 每次调用固定价格 | Fixed price per invocation |
## 用户管理 (User Management)
| 中文 | English | 说明 | Description |
|------|---------|------|-------------|
| 超级管理员 | Root User | 最高权限管理员 | Administrator with highest privileges |
| 管理员 | Admin User | 系统管理员 | System administrator |
| 普通用户 | Normal User | 普通权限用户 | Regular user with standard privileges |
## 充值与兑换 (Recharge & Redemption)
| 中文 | English | 说明 | Description |
|------|---------|------|-------------|
| 充值 | Top Up | 为账户增加额度 | Add quota to account |
| 兑换码 | Redemption Code | 可兑换额度的代码 | Code that can be redeemed for quota |
## 渠道管理 (Channel Management)
| 中文 | English | 说明 | Description |
|------|---------|------|-------------|
| 渠道 | Channel | API服务提供通道 | API service provider channel |
| 密钥 | Key | API访问密钥 | API access key |
| 优先级 | Priority | 渠道选择优先级 | Channel selection priority |
| 权重 | Weight | 负载均衡权重 | Load balancing weight |
| 代理 | Proxy | 代理服务器地址 | Proxy server address |
| 模型重定向 | Model Mapping | 请求体中模型名称替换 | Model name replacement in request body |
## 安全相关 (Security Related)
| 中文 | English | 说明 | Description |
|------|---------|------|-------------|
| 两步验证 | Two-Factor Authentication | 为账户提供额外安全保护的验证方式 | Additional security verification method for accounts |
| 2FA | Two-Factor Authentication | 两步验证的缩写 | Abbreviation for Two-Factor Authentication |
## 计费相关 (Billing Related)
| 中文 | English | 说明 | Description |
|------|---------|------|-------------|
| 倍率 | Ratio | 价格计算的乘数因子 | Multiplier factor used for price calculation |
| 倍率 | Multiplier | 价格计算的乘数因子(同义词) | Multiplier factor used for price calculation (synonym) |
## 翻译注意事项 (Translation Guidelines)
- **提示 (Prompt)** = 模型输入内容 / Model input content
- **补全 (Completion)** = 模型输出内容 / Model output content
- **倍率 (Ratio)** = 价格计算的乘数因子 / Multiplier factor for price calculation
- **额度 (Quota)** = 可用的用户服务额度,有时也翻译为 Credit / Available service quota for users, sometimes also translated as Credit
- **Token** = 根据上下文可能指 / Depending on context, may refer to:
- API访问令牌 (API Token)
- 模型处理的文本单元 (Text Token)
- 系统访问令牌 (Access Token)
---
**贡献说明**: 如发现术语翻译不一致或有更好的翻译建议,欢迎提交 Issue 或 Pull Request。
**Contribution Note**: If you find any inconsistencies in terminology translations or have better translation suggestions, please feel free to submit an Issue or Pull Request.
+107
View File
@@ -0,0 +1,107 @@
# Русский глоссарий (Russian Glossary)
Данный раздел предоставляет стандартные переводы ключевой терминологии проекта на русский язык для обеспечения согласованности и точности переводов.
This section provides standard Russian translations for key project terminology to ensure consistency and accuracy in translations.
## Основные концепции (Core Concepts)
- Допускается использовать символы Emoji в переводе, если они были в оригинале.
- Допускается использование сугубо технических терминов, если они были в оригинале.
- Допускается использование технических терминов на английском языке, если они широко используются в русскоязычной технической среде (например, API).
| Китайский | Русский | Английский | Описание |
|-----------|--------|-----------|----------|
| 倍率 | Коэффициент | Ratio/Multiplier | Множитель для расчета цены. **Важно:** В контексте расчетов цен всегда использовать "Коэффициент", а не "Множитель" для обеспечения консистентности терминологии |
| 令牌 | Токен | Token | Учетные данные API или текстовые единицы |
| 渠道 | Канал | Channel | Канал доступа к поставщику API |
| 分组 | Группа | Group | Классификация пользователей или токенов |
| 额度 | Квота | Quota | Доступная квота услуг для пользователя |
## Модели (Model Related)
| Китайский | Русский | Английский | Описание |
|-----------|--------|-----------|----------|
| 提示 | Промпт/Ввод | Prompt | Содержимое ввода в модель |
| 补全 | Вывод | Completion | Содержимое вывода модели. **Важно:** Не использовать "Дополнение" или "Завершение" - только "Вывод" для соответствия технической терминологии |
| 输入 | Ввод | Input/Prompt | Содержимое, отправляемое в модель |
| 输出 | Вывод | Output/Completion | Содержимое, возвращаемое моделью |
| 模型倍率 | Коэффициент модели | Model Ratio | Коэффициент тарификации для разных моделей |
| 补全倍率 | Коэффициент вывода | Completion Ratio | Дополнительный коэффициент тарификации для вывода |
| 固定价格 | Цена за запрос | Price per call | Цена за один вызов |
| 按量计费 | Оплата по объему | Pay-as-you-go | Тарификация на основе использования |
| 按次计费 | Оплата за запрос | Pay-per-view | Фиксированная цена за вызов |
## Управление пользователями (User Management)
| Китайский | Русский | Английский | Описание |
|-----------|--------|-----------|----------|
| 超级管理员 | Суперадминистратор | Root User | Администратор с наивысшими привилегиями |
| 管理员 | Администратор | Admin User | Системный администратор |
| 普通用户 | Обычный пользователь | Normal User | Пользователь со стандартными привилегиями |
## Пополнение и обмен (Recharge & Redemption)
| Китайский | Русский | Английский | Описание |
|-----------|--------|-----------|----------|
| 充值 | Пополнение | Top Up | Добавление квоты на аккаунт |
| 兑换码 | Код купона | Redemption Code | Код, который можно обменять на квоту |
## Управление каналами (Channel Management)
| Китайский | Русский | Английский | Описание |
|-----------|--------|-----------|----------|
| 渠道 | Канал | Channel | Канал поставщика API |
| API密钥 | API ключ | API Key | Ключ доступа к API. **Важно:** Использовать "API ключ" вместо "API токен" для большей точности и соответствия общепринятой русскоязычной технической терминологии. Термин "ключ" более точно отражает функционал доступа к ресурсам, в то время как "токен" чаще ассоциируется с текстовыми единицами в контексте обработки языковых моделей. |
| 优先级 | Приоритет | Priority | Приоритет выбора канала |
| 权重 | Вес | Weight | Вес балансировки нагрузки |
| 代理 | Прокси | Proxy | Адрес прокси-сервера |
| 模型重定向 | Перенаправление модели | Model Mapping | Замена имени модели в теле запроса |
| 供应商 | Поставщик | Provider/Vendor | Поставщик услуг или API |
## Безопасность (Security Related)
| Китайский | Русский | Английский | Описание |
|-----------|--------|-----------|----------|
| 两步验证 | Двухфакторная аутентификация | Two-Factor Authentication | Дополнительный метод проверки безопасности для аккаунтов |
| 2FA | 2FA | Two-Factor Authentication | Аббревиатура двухфакторной аутентификации |
## Рекомендации по переводу (Translation Guidelines)
### Контекстуальные варианты перевода
**Промпт/Ввод (Prompt/Input)**
- **Промпт**: При общении с LLM, в пользовательском интерфейсе, при описании взаимодействия с моделью
- **Ввод**: При тарификации, технической документации, описании процесса обработки данных
- **Правило**: Если речь о пользовательском опыте и взаимодействии с AI → "Промпт", если о техническом процессе или расчетах → "Ввод"
**Token**
- API токен доступа (API Token)
- Текстовая единица, обрабатываемая моделью (Text Token)
- Токен доступа к системе (Access Token)
**Квота (Quota)**
- Доступная квота услуг пользователя
- Иногда переводится как "Кредит"
### Особенности русского языка
- **Множественные формы**: Требуется правильная реализация множественных форм (_one,_few, _many,_other)
- **Падежные окончания**: Внимательное отношение к падежным окончаниям в технических терминах
- **Грамматический род**: Согласование рода технических терминов (например, "модель" - женский род, "канал" - мужской род)
### Стандартизированные термины
- **Вывод (Completion)**: Содержимое вывода модели
- **Коэффициент (Ratio)**: Множитель для расчета цены
- **Код купона (Redemption Code)**: Используется вместо "Код обмена" для большей точности
- **Поставщик (Provider/Vendor)**: Организация или сервис, предоставляющий API или AI-модели
---
**Примечание для участников:** При обнаружении несогласованности в переводах терминологии или наличии лучших предложений по переводу, не стесняйтесь создавать Issue или Pull Request.
**Contribution Note for Russian:** If you find any inconsistencies in terminology translations or have better translation suggestions for Russian, please feel free to submit an Issue or Pull Request.