接入文档

Mock 任务按现有预估计费规则结算测试积分,实际用量型计费的账本标记 mock_estimate,不伪造供应商用量。真实任务仍遵循原结算配置;缺失用量配置为待对账时,不自动按预估扣费。

通道 mock 模式仍执行素材校验、有效期检查及必要的真实存储上传;所有已注册视频生成通道(星妙、APIMart、OpenAI Videos、Seedance 兼容、MiniMax、万相、腾讯 MPS、PixVerse,以及换人/换脸包装通道)执行真实请求准备,随后返回模拟任务,不发送视频生成请求。PixVerse 会执行必要的素材上传和人物选择读取,在生成入口前停止;换人/换脸校验配置并准备实际 transport,但不执行生成后审核。其他非视频协议执行公共素材准备。存储请求可能产生流量或存储费用;模拟成功不代表上游已接受请求。有效期充足的素材链接直接复用,必要暂存使用应用指定的存储通道,支持 CloudBase、COS、TOS、OSS。

Mock 生成成功后仍需完成结果存储才能交付。托管样例的临时地址在排队或重试后可按原对象重新签发;不会因通道样例配置变化而替换原任务内容。腾讯 MPS Mock 会在项目当前 CloudBase/COS 桶内使用服务端 CopyObject 生成任务独占结果,再按原生存储引用登记,不经过浏览器下载上传,也不会调用付费 MPS 处理。客户端应以 delivery_status=delivered 判断交付完成,persisting 包含等待传输执行。任务维护的字段缺失返回 JOB_MAINTENANCE_SCHEMA_NOT_READY,其他领取错误返回 JOB_MAINTENANCE_CLAIM_FAILED;两者均不代表模型生成失败,恢复后查询原 job_id。

这是第三方接入 CommonSrv 的统一入口文档,覆盖浏览器 / Node SDK、REST、OpenAI 兼容接口、同步与异步 AI、鉴权、计费和回调,并按场景导航到专题字段文档。支持 SDK 时优先使用:CommonSrv.billing.* / CommonSrv.ai(...) /CommonSrv.endpoints.* / CommonSrv.auth.* / CommonSrv.requireUser() / CommonSrv.storage.*。 token、续期、401 重试、异步任务轮询、跨标签同步全部由 SDK 内部处理,不需要、也不应该手动读取 access_token 或自建轮询。

从本页开始,按接入场景继续:浏览器 / Node 使用本页 SDK 核心流程·iOS / Android / macOS / Windows 多端接入 →·REST / OpenAI 兼容完整字段 →

Flutter 原生 SDK 会话:使用 SecureCommonSrvSessionStorage 保存凭据;await client.logout() 会清除本机会话,并使此前未完成的登录、交换、刷新和鉴权请求失效。 这些旧操作返回 CommonSrvException('SESSION_CHANGED', 409),不得当作新会话继续展示或重试。 SDK 串行写入安全存储,防止旧刷新在退出后恢复凭据。宿主应在退出时立即关闭当前工作区, 并处理存储失败;安全存储的读取、写入、删除及格式错误直接向上传播,不降级为内存会话, 也不自动删除损坏凭据。存储键应按服务地址与 client_id 隔离,登录成功以安全存储写入成功为准。 这不等于服务端全设备撤销。网络错误与 5xx 不主动删除凭据,离线身份和业务权限由宿主明确限制; 401 经 SDK 刷新后仍失败才需要重新认证。每个安全存储键应只由一个 SDK 客户端实例管理。

AI / LLM 机读契约(MACHINE-READABLE CONTRACT)

AI 助手请严格照搬下方 === BEGIN CONTRACT === 块。支持 SDK 的运行时不要重复实现 token 续期和轮询; 无法使用 SDK 时先按本页 REST 核心流程接入,需要完整字段时进入 REST / OpenAI 兼容专题文档。

=== BEGIN CONTRACT (commonsrv SDK, v3) ===

PRODUCT_NAME: CommonSrv (通用账户服务)
DOC_URL:      https://<your-commonsrv-host>/docs
SDK_BROWSER:  https://<your-commonsrv-host>/sdk/commonsrv-browser.js
SDK_NODE:     https://<your-commonsrv-host>/sdk/commonsrv-node.mjs
SDK_VERSION:  3.x — namespaced API only

INTEGRATION_STYLE: SDK_FIRST_WITH_DOCUMENTED_REST_FALLBACK
  - Prefer the CommonSrv.* methods listed below when the runtime supports the SDK.
  - Do NOT hand-roll iframe / postMessage / refresh-token loops.
  - Do NOT call any /api/public/v1/* endpoint via fetch/axios yourself.
  - REST fallback core flow is documented later on this page; full field reference: https://<your-commonsrv-host>/docs-rest.

ALLOWED_BROWSER_API:
  - CommonSrv.configure({ clientId, lang?, autoReloadOnLangChange?, autoReloadOnLogout? })  # once at boot
  - CommonSrv.lang.getLang()                                     # 读当前语言(写入统一走 profile.open,无 setLang)
  - CommonSrv.requireUser({ view? })                             # 未登录自动弹登录
  - CommonSrv.user()                                             # 同步读,未登录 null
  - CommonSrv.auth.openLogin({ view?, mode?, container? })
  - CommonSrv.auth.logout()                                      # await
  - CommonSrv.auth.onChange(fn)
  - CommonSrv.auth.me()                                          # 服务端权威用户资料(含 role_codes)
  - CommonSrv.auth.getAccessToken()                              # 传给你自己后端用
  - CommonSrv.auth.hasRole(code)                                 # 同步读缓存 role_codes,仅 UI 展示用
  - CommonSrv.auth.roleCodes()                                   # 同步读缓存 role_codes
  - CommonSrv.billing.open({ mode?, container?, onEvent? })
  - CommonSrv.billing.balance()                                  # { balance, total_earned, total_spent }
  - Billing catalog plan.storage_quota_bytes                     # 创作空间字节额度;0 不是无限
  - CommonSrv.ai(modelKey, input, opts?)                         # 同步 LLM
  - CommonSrv.ai.submit(modelKey, input, opts?)                  # 异步任务
  - CommonSrv.ai.wait(jobId, { onProgress? })                    # SDK 内置轮询
  - CommonSrv.ai.get(jobId)
  - CommonSrv.ai.models()
  - CommonSrv.endpoints.list()
  - CommonSrv.endpoints.call(key, input, opts?)
  - CommonSrv.storage.uploadManaged(file, opts?)                 # { id, status, url }
  - CommonSrv.storage.resolveManaged([id])                      # 按资源 ID 读取
  - CommonSrv.invite.open({ mode?, container? })                 # 一体化邀请中心
  - CommonSrv.profile.open({ mode?, container? })                # 个人中心(昵称 / 绑定 / 语言 / 退出)
  - CommonSrv.on(evt, cb)                                        # "auth:change" | "billing:paid" | "ai:job:update" | "lang:change"

# 下划线前缀方法(_rawSession / _refresh / ...)都是内部实现,随时会改,不要用。

BUSINESS_MEDIA_SERVER_INTEGRATION_ONLY:
  - POST /media/assets/intents, POST /media/assets/:id/upload-ticket,
    POST /media/assets/:id/complete, POST /media/assets/:id/abort, GET /media/assets/:id,
    POST /media/assets/resolve, DELETE /media/assets/:id
  - 浏览器直传由你的服务端代为使用 CommonSrv JWT 取得短期 Ticket;不能把 JWT、CloudBase 私钥或 CAM 凭据交给浏览器。
  - canonical fields and browser-direct rollout boundary: https://<your-commonsrv-host>/docs-rest

ALLOWED_NODE_API (commonsrv-node.mjs — 你的后端在用):
  - new CommonSrv({ apiKey, baseUrl?, webhookSecret?, callbackUrl? })   # 构造,apiKey = 项目 API Key(服务器保存)
  - cs.configure({ apiKey?, baseUrl?, webhookSecret?, callbackUrl? })   # 运行时补配置
  - cs.mount(app, { path?, webhookPath?, getOpts?, onEvent?, onPaid?, onRefunded?, onTaskDeliveryReady?, onTaskEvent? })
                                                                        # Express 一键挂载 checkout + 统一 webhook
  # ---- checkout ----
  - cs.checkout.create({ user_ref, plan_id? | credit_pack_id?, callback_url?, metadata? })   # 返回 { checkout_url }
  - cs.checkout.express(getOpts)                                        # Express handler:签发并 302 到 checkout_url
  - cs.checkout.start(req, res, optsOrGetter)                           # Node 原生 http 版本
  # ---- 项目统一 webhooks(支付、任务交付与未来事件)----
  - cs.webhooks.verify({ payload, signature, secret?, toleranceSec? })  # 手动验签
  - cs.webhooks.handler({ onEvent?, onOrderPaid?, onOrderRefunded?, onTaskDeliveryReady?, onTaskEvent?, secret? })
  - cs.webhooks.express(onEvent, opts?)                                 # Express-only 简版
  - cs.tasks.results(jobIds?)                                           # 项目后端读取待关联任务结果
  - cs.tasks.assets(assetIds)                                           # 项目后端读取结果资产
  - cs.tasks.sync(cursor?)                                              # 项目后端只读同步任务状态
  # ---- 代某个 app_user 调用(先 useToken 再调用)----
  - cs.wallet.balance(userAccessToken)                                  # { balance, total_earned, total_spent }
  - cs.ai.useToken(userAccessToken).submit(modelKey, input, opts?)      # 后台代跑长任务
  - cs.ai.useToken(userAccessToken).wait(jobId, { onProgress? })
  - cs.ai.useToken(userAccessToken).get(jobId)
  - cs.ai.useToken(userAccessToken).invoke(modelKey, input, opts?)      # 同步 LLM
  - POST /media/assets/intents + upload-ticket + complete            # Node 侧资源 ID 上传
  - cs.storage.useToken(userAccessToken).getReadUrl(objectKey, expiresIn?)
  - cs.storage.useToken(userAccessToken).remove(objectKey)
  # ---- 身份 / 角色(业务权限校验的唯一可信源)----
  - cs.auth.me(userAccessToken)                                         # 返回 { id, role_codes, ... },token 失效抛 401
  - cs.auth.roleCodes(userAccessToken)                                  # string[]
  - cs.auth.hasRole(userAccessToken, code | code[])                     # boolean
  - cs.auth.requireUser()                                               # Express 中间件:需登录,注入 req.commonsrvUser
  - cs.auth.requireRole(code | code[])                                  # Express 中间件:需指定角色



BANNED_PATTERNS:
  - 从 localStorage / cookie / _rawSession() 读取 access_token / refresh_token
  - 自己拼 "Authorization: Bearer <token>" 打 commonsrv 的 REST
  - SDK 可覆盖时仍手写 fetch / axios、token 续期或任务轮询
  - 调用任何下划线前缀方法
  - setInterval 自建 /ai/jobs/:id 轮询(用 CommonSrv.ai.wait)
  - 把 token 传给第三方源

# ---------- CANONICAL SNIPPETS ----------

# 0) 前置 —— 所有页面都要先引一次 SDK 并 configure(只做一次,之后各处直接用 window.CommonSrv.*)
<script src="https://<your-commonsrv-host>/sdk/commonsrv-browser.js"></script>
<script>
  CommonSrv.configure({ clientId: "cli_xxxxxxxxxxxxxxxx" });
</script>

# 1) 要求登录(未登录自动弹登录浮层,登录完 resolve 用户对象)
const user = await CommonSrv.requireUser();

# 2) 同步 AI 调用(LLM)
const reply = await CommonSrv.ai("google/gemini-2.5-flash", {
  messages: [{ role: "user", content: "hi" }],
});

# 3) 读取余额
const { balance } = await CommonSrv.billing.balance();


# 4) Buy credits (one line):
document.querySelector("#buy").addEventListener("click", () =>
  CommonSrv.billing.open({ onEvent: (d) => d.type === "order.paid" && location.reload() })
);

# 5) Async video / image / voice — submit then wait:
const { job_id } = await CommonSrv.ai.submit("kling-v2", { prompt: "a running cat" });
const done   = await CommonSrv.ai.wait(job_id, { onProgress: (j) => console.log(j.status, j.progress) });
if (done.status === "succeeded") showVideo(done.output);

# 6) Tool endpoint (音色克隆 / 抖音解析 / …):
const r = await CommonSrv.endpoints.call("minimax-voice-design",
  { prompt: "温柔女声,30 岁左右", gender: "female", age: "young",
    preview_text: "你好,这是一段试听文本。" },   // 必填,且按其字符数计费
  { meta: { feature: "音色克隆" }, ref: { type: "your_app", id: "voice-001" } });
// r: { ok, output, credits_charged, balance }

# 7) Identify the commonsrv user on YOUR OWN backend
#    浏览器:向「你自己的后端」发请求,捎带上 commonsrv 的 access_token
#    (下面的 fetch 是浏览器原生 fetch,不是 CommonSrv.rest;/api/... 是你自己域名下的接口,不是 commonsrv 的)
#    你的后端:拿到 Bearer 后调 commonsrv REST GET /v1/auth/app/me 校验用户身份(完整字段见 /docs-rest)
#    或者直接用 Node SDK:cs.auth.me(token) / cs.auth.requireRole("admin") 中间件
const token = await CommonSrv.auth.getAccessToken();      // 自动续期
await fetch("/api/my-server-fn", { headers: { Authorization: `Bearer ${token}` } });

# 7b) 前端根据角色显隐 UI(真正权限校验必须在你自己后端再做一次)
if (CommonSrv.auth.hasRole("admin")) document.querySelector("#admin-panel").hidden = false;
console.log(CommonSrv.auth.roleCodes());   // ["user"] / ["admin","operator"] / []

# 8) 邀请奖励
await CommonSrv.invite.open();

# 9) 个人中心(含语言切换)
await CommonSrv.profile.open();


# ---------- NODE SDK (server-to-server) —— 一般不用,仅自建订单系统 / 服务端代提交 AI 任务时才需要 ----------
# 详见下文「功能一 · ② 后端(Node · 挂载「开始购买」+「Webhook」)— 一般不需要」折叠区块。

# ---------- ERROR HANDLING ----------
# 所有高阶方法失败会 throw Error,带 .status / .code / .data。
# 401 自动重试后仍 401 → 用户实际已登出 → 重新 CommonSrv.requireUser。
# 402 = 积分不足 → 引导 CommonSrv.billing.open。
# 429 = 限流。
# CommonSrv.ai(<异步模型>, ...) 会抛 code="USE_JOBS_ENDPOINT",改用 .submit / .wait。

=== END CONTRACT ===

人类阅读者:上方是浓缩版;分节示例见下方。

v3 一分钟接入 — 绝大多数场景只需这 6 行
<script src="https://<your-commonsrv-host>/sdk/commonsrv-browser.js"></script>
<script>
  CommonSrv.configure({ clientId: "<your_client_id>" });

  const user  = await CommonSrv.requireUser();                                  // 未登录自动弹登录
  const reply = await CommonSrv.ai("google/gemini-2.5-flash", {                 // 同步 AI
    messages: [{ role: "user", content: "hi" }],
  });
  const { balance } = await CommonSrv.billing.balance();                        // 余额
  document.querySelector("#buy").onclick = () =>
    CommonSrv.billing.open({ onEvent: (d) => d.type === "order.paid" && location.reload() });

  // 异步(视频 / 图像 / 语音)
  // const { job_id } = await CommonSrv.ai.submit("kling-v2", { prompt: "a cat" });
  // const done   = await CommonSrv.ai.wait(job_id, { onProgress: p => log(p.progress) });
</script>

SDK 下载

本服务只支持 SDK 接入。浏览器端 1 个文件,Node 端 1 个文件。

也可直接 <script src="https://<your-commonsrv-host>/sdk/commonsrv-browser.js"></script> 引用。

两个 SDK 各干什么?怎么配合?

两个 SDK 不是二选一,也不是"浏览器 SDK 通过服务端 SDK 转发"。它们分别驻扎在浏览器和你的后端,各自直连 CommonSrv,处理只有自己那一侧能安全做的事,再通过用户身份(app_user)和订单 Webhook 串起来。

能力Browser SDK
commonsrv-browser.js
Node SDK
commonsrv-node.mjs
鉴权凭证终端用户 access_token(登录/续期/localStorage)项目 API Key(只在后端,绝不下发浏览器)
登录 / 注册 / 找回密码
(app_user 用户系统)
✅ requireUser() / auth.* / 登录弹窗(用户身份天然在浏览器完成)—(Node SDK 不参与 app_user 的登录/注册/token 校验;session 由 Browser SDK + CommonSrv 后端直连维护)
Webhook 接收 & 验签— (浏览器收不到 webhook)✅ cs.mount() / cs.webhooks.handler():验签 + 路由 onPaid/onRefunded,权益开通的唯一可信源
读取用户余额✅ billing.balance()(读当前登录用户)✅ cs.wallet.balance(userAccessToken)(后端读某个 app_user 余额,用于服务端渲染/风控)
购买 / 充值弹窗✅ billing.open()(popup / iframe / redirect)一般不用 cs.checkout.start() 服务端签发 checkout(仅自建订单/后台发单场景)
调用 AI(同步/异步)✅ ai(...) / ai.submit/wait(自动扣当前登录用户的积分)一般不用 cs.ai.useToken(userJWT).submit(...)(仅后台代用户跑长任务/定时任务)
接口 endpoints.call✅ 前端直调(发短信、抓数据等)一般不用 后端调用(同上,用 API Key 或 useToken)
校验用户身份 / 角色✅ auth.hasRole(code) / auth.me()(仅 UI 展示)✅ cs.auth.me(token) / cs.auth.requireRole("admin") Express 中间件(业务权限校验的唯一可信源)
对象存储直传(OSS / TOS / COS)✅ storage.uploadManaged(file)(浏览器直传并返回资源 ID)一般不用 按资源 ID 接口申请上传、确认与读取
配合方式(典型时序)
浏览器 (Browser SDK)                你的后端 (Node SDK)                CommonSrv
      │                                    │                                │
① 用户点"购买"                             │                                │
   billing.open()  ─────────────────────────────────────────────────────►   │  弹出购买页
② 用户付款完成                             │                                │
   (前端只收到 UX 事件 order.paid, 用来刷新 UI;权益开通不能只信这个)        │
      │                                    │◄── ③ Webhook (签名)  ──────    │
      │                                    │    cs.mount 内部验签           │
      │                                    │    onPaid(data) → 你落库/发权益 │
      │                                    │                                │
④ 之后调用 AI                              │                                │
   ai.submit(...)  ─────────────────────────────────────────────────────►   │  扣积分 + 派发上游
      │                                    │                                │
   (可选) 长任务想在后端跑:                │                                │
      │    传当前用户 access_token 给后端 → cs.ai.useToken(t).submit(...)  ─►
  • Browser SDK 必装:只要终端用户在浏览器里操作(登录 / 付款 / 用 AI),就用它。购买弹窗、AI 调用、endpoints、对象存储上传都直接在这里做,不需要经过你的后端中转。
  • Node SDK 什么时候真的需要装:只在下面这些场景才装,不要平白拉入:
    • 接 Webhook(强烈建议):付款成功后把订单 / 权益写进你自己的数据库——这是权益开通的唯一可信源,前端 order.paid 事件只做 UX。
    • 后台看某用户余额:服务端要展示 / 风控 / 门槛判断时用 cs.wallet.balance(userAccessToken)。
    • (不常用)自建订单页、定时任务代用户跑 AI、Node 侧本地文件传 对象存储——见上表【一般不用】项。
  • 不做的事:Browser SDK 不会把请求转发给你的后端再由后端打 CommonSrv;Node SDK 也不是浏览器 SDK 的代理层。两边都直连 CommonSrv,只是各自带的凭证不同:浏览器带用户 access_token,Node 带项目 API Key(或用 useToken 临时借用用户 token)。

共用规范:多语言(i18n)

语言代号统一走 BCP-47(en / zh-CN / zh-TW / ja / ko / es…)。 业务端要做的事:① 切 UI 语言(SDK 打开的所有弹窗跟随);② 在请求 meta 里给出需要多语言展示的字段。

① UI 语言

SDK 自动跟随用户账户偏好 / 浏览器语言,无需在 configure 中传 lang。用户切换语言统一在 CommonSrv.profile.open() 集成页面内完成,业务端不需要也不应该再做语言切换 UI。当前 UI 已上线 en / zh-CN / zh-TW,后续持续新增。

// 运行时读取(登录时来自账户,未登录来自本地/浏览器)
const lang = await CommonSrv.lang.getLang();

② meta.feature —「功能」显示名(用户端唯一入口)

CommonSrv.ai(...) / CommonSrv.ai.submit(...) / CommonSrv.endpoints.call(...) 的 opts.meta.feature可以供 commonsrv 系统中需要多语言切换显示的时候使用,比如用户积分明细的功能列。两种写法:

// ① 多语(推荐,默认使用这种)
//    只要你的界面需要跟随用户语言切换,一律传多语对象。
{
  meta: {
    feature: {
      "zh-CN": "选品定稿",
      "zh-TW": "選品定稿",
      "en":    "Picking Finalize",
      "ja":    "商品選定"
    }
  }
}

// ② 单语 —— 仅当你确定该场景不需要多语言切换时使用
//    命中内置词典会自动翻译,未命中就原样展示。
{ meta: { feature: "图像生成" } }

多语对象回退规则:当前语言未命中 → 回退 en → 回退字典里任意首个非空值。至少提供 en 一种以保证兜底可读。

功能一:购买页面(集成页面接口)

前端 1 个按钮 + 后端 1 个接口 + Webhook 1 个处理函数。金额与商品由服务端决定,权益开通以 Webhook 为准。

先在 项目管理 复制 API Key 和 Webhook Secret,并把你应用的域名(开发 / 预览 / 生产)加到 「允许的来源域名」。

① 前端(1 行绑定按钮)

<script src="https://<your-commonsrv-host>/sdk/commonsrv-browser.js"></script>
<button id="buy">购买 / 充值</button>
<script>
  CommonSrv.configure({ clientId: "<your_client_id>" });
  document.querySelector("#buy").addEventListener("click", () =>
    CommonSrv.billing.open({
      onEvent: (e) => e.type === "order.paid" && location.reload(),
    })
  );
</script>

打开形式(mode)

  • popup(默认):SDK 自建的遮罩 + 居中 iframe + 关闭按钮;PC 上占页面 70%(最大 900×800),手机自动全屏。点击弹窗外的遮罩、可见关闭按钮或 Esc 可退出;关闭按钮位于内容区外,手机端有独立顶部空间,不覆盖购买页内的按钮。
  • iframe:只创建 <iframe> 挂到你指定的 container,由你控制布局。
  • redirect:整页跳转,支付完成后按 return_url 跳回。
// popup(默认)
CommonSrv.billing.open({ onEvent: (e) => e.type === "order.paid" && location.reload() });

// iframe
CommonSrv.billing.open({
  mode: "iframe",
  container: document.getElementById("checkout-box"),
  onEvent: (e) => { if (e.type === "order.paid") location.reload(); },
});

// redirect
CommonSrv.billing.open({ mode: "redirect" });
② 后端(Node · 挂载「开始购买」+「Webhook」)— 一般不需要

只显示 CommonSrv 余额时可由前端 SDK 刷新。应用需要保存支付权益、把生成结果写进分镜或资产记录时, 后端必须接入一个统一 Webhook;支付与任务事件使用同一个地址和签名密钥。

import express from "express";
import { CommonSrv } from "./commonsrv-node.mjs";

const app = express();
const cs = new CommonSrv({
  apiKey: process.env.COMMONSRV_API_KEY,
  webhookSecret: process.env.COMMONSRV_WEBHOOK_SECRET,
  baseUrl: "https://<your-commonsrv-host>",
});

cs.mount(app, {
  getUserRef: (req) => req.user?.id,
  onPaid: async (data) => {
    // data: { order_no, user_ref, plan_code, pack_code, amount_cents, ... }
    await refreshCredits(data.user_ref);
  },
  onTaskDeliveryReady: async (data, event) => {
    await bindGeneratedResult(data.task_id, event.id);
  },
});

框架无关写法(Hono / Fastify / 原生 Node)

// 开始购买
app.post("/api/billing/start", (req, res) =>
  cs.checkout.start(req, res, { getUserRef: (req) => req.user?.id })
);

// Webhook(内部完成验签 + 事件路由)
app.post("/webhooks/commonsrv", cs.webhooks.handler({
  onOrderPaid:     async (data) => { await refreshCredits(data.user_ref); },
  onOrderRefunded: async (data) => { await revokeAccess(data.user_ref); },
  onTaskDeliveryReady: async (data, event) => {
    // data.task_id 已完成生成和转存。用项目 API Key 读取该任务结果,
    // 按提交时的 delivery_binding 幂等写入业务表,再提交资源引用回执。
    await bindGeneratedResult(data.task_id, event.id);
  },
  onEvent: async (event) => { await recordKnownEvent(event.id, event.event); },
}));

后台只配置一个项目统一 Webhook URL。CommonSrv 使用统一信封{ id, version, event, occurred_at, project, data },并在请求头发送x-commonsrv-event-id、x-commonsrv-event、x-commonsrv-delivery-attempt 与签名。接收端以 id 幂等: 完成业务事务后返回 2xx,临时失败返回非 2xx 让 CommonSrv 自动退避重试。

必须遵守: API Key 与 Webhook Secret 只在后端; 权益开通只信 onPaid(前端回调仅作 UX 提示)。

后端代提交 AI 任务(一般不用)

仅当需要在自己服务端代 app_user 提交长任务时使用;常规场景直接在浏览器用 CommonSrv.ai.submit / .wait 即可。

cs.ai.useToken(userAccessToken);   // 传入该用户的 access_token
const { job_id } = await cs.ai.submit("kling-v2", { prompt: "..." });
const done   = await cs.ai.wait(job_id);

校验前端传上来的用户身份 / 角色(推荐)

浏览器把 Bearer <access_token> 发到你自己后端,后端用 cs.auth.* 二次校验;不要只信客户端传上来的角色字段。

// 只要求登录
app.get("/api/any-login", cs.auth.requireUser(), (req, res) => {
  // req.commonsrvUser = { id, project_id, display_name, role_codes, ... }
  res.json({ hello: req.commonsrvUser.display_name });
});

// 要求管理员角色(数组表示"任一即可")
app.get("/api/admin/x", cs.auth.requireRole(["admin", "operator"]), (req, res) => {
  res.json({ ok: true });
});

// 手动校验(框架无关)
const user  = await cs.auth.me(accessToken);              // { role_codes: ["admin"], ... }
const isAdm = await cs.auth.hasRole(accessToken, "admin"); // true / false

功能二:登录注册页面(集成页面接口)+ 用户信息 API

CommonSrv.requireUser() 一行搞定:未登录自动弹登录 → 之后所有 SDK 调用自动带 Bearer、静默续期、401 自动重试、跨标签同步。

登录页按「项目管理 → 配置 → 登录配置」生效(启用的登录方式、是否开放注册、是否人机验证);修改后立即跟随,无需改代码。 必须先在 项目管理 拿到 clientId。

三步接入

<script src="https://<your-commonsrv-host>/sdk/commonsrv-browser.js"></script>
<script>
  // ① 配置一次
  CommonSrv.configure({ clientId: "<your_client_id>" });

  // ② 登录(未登录自动弹登录页;也可用 CommonSrv.auth.openLogin({ view }) 主动打开)
  const user = await CommonSrv.requireUser();

  // ③ 之后调各功能命名空间即可(token 全自动)
  const wallet = await CommonSrv.billing.balance();

  // 监听会话变化(含跨标签页同步)
  CommonSrv.auth.onChange((e) => { if (!e.session) location.href = "/"; });

  // 退出登录
  // await CommonSrv.auth.logout();
</script>

登录页打开形式(mode)

// popup(默认)
await CommonSrv.auth.openLogin();

// iframe — 内嵌到页面某个区域
await CommonSrv.auth.openLogin({ mode: "iframe", container: document.getElementById("login-box") });

// redirect — 整页跳转
CommonSrv.auth.openLogin({ mode: "redirect" });
⛔ 红线: 永远不要自己拼 Authorization: Bearer …, 永远不要读取 access_token / refresh_token。 所有调用都走 CommonSrv.billing.* / CommonSrv.ai(...) / CommonSrv.endpoints.*。

用户信息 API

两个读用户的方法:CommonSrv.user() 同步读缓存(首屏 / 渲染判断用);CommonSrv.auth.me() 异步拉服务端权威资料(含最新 email / phone / metadata,也可用来校验 token 是否仍有效)。常规业务用 user() 即可。

字段类型必填说明
CommonSrv.user()user | null可选同步读取当前用户;未登录返回 null。
CommonSrv.requireUser({ view? })Promise<user>可选未登录自动弹登录,登录后 resolve 用户对象。
CommonSrv.auth.me()Promise<user>可选异步拉一次服务端权威用户资料。
// 同步读缓存
const u = CommonSrv.user();

// 异步拉服务端权威
const me = await CommonSrv.auth.me();
// { id, email?, phone?, metadata, created_at, ... }

功能三:积分相关

读取当前用户的积分余额与累计获得 / 消费。SDK 已封装:

const { balance, total_earned, total_spent, updated_at } = await CommonSrv.billing.balance();

功能四:AI 调用与计费(模型管理 · LLM / 图像 / 视频 / TTS)

统一入口:只指定 model_key,通道路由 / 权重 / 降级由服务端调度。按 token / 时长 / 张数自动扣积分。

AI 调用计费优先使用实际通道的用户价格,通道未配置时才使用模型价格兜底;模型价格可以留空。调用前仅按本次可用通道的有效规则估算并预授权,故障切换后按实际通道规则结算。未配置有效价格的通道不参与本次分发;没有可用价格时返回 PRICING_RULE_MISSING。

APIMart Gemini 3.8 Flash 通道通过同步聊天接口调用;对外 model_key 以当前模型目录为准。上游即使返回分块事件,服务端也会合并为一次聊天响应并使用上游最终 token 用量结算。输入、缓存输入和输出按实际 token 分别计费;用户价为 APIMart 对应 USD 成本乘以 1.2,再按当前积分汇率换算。此入口不提供 APIMart 的 Google Web Search 或显式上下文缓存创建能力,不能用本模型的 token 价格估算这两类额外收费。

genllm 与 genlongtext 的 APIMart GPT-6 Astra、Claude Opus 5.5 通道按上游回传的实际 token 用量结算。输入、缓存读取、缓存写入和输出分别计价;Astra 输入超过 272,000 token 时整次调用使用长上下文价格,Claude 缓存写入区分 5 分钟与 1 小时。用户价格为 APIMart 当前配置的渠道 USD 单价乘以 1.2,再按调用时积分汇率换算。APIMart 的聊天响应不返回实际货币费用,因此此处按服务端费率规则计算,不使用“按服务方回传实际费用结算”。发起时缓存是否命中尚不可知,服务端先预授权,完成后依实际用量找平;最终价格以服务端结算记录为准。

按 token 用量计费的视频生成任务,创建时按请求参数匹配价格档位并预授权;实际用量尚未产生时,不会因 token 数为零而拒绝已匹配的档位。任务完成后按服务商回传的实际 token 用量,用提交时选中通道的价格结算并记录价格、用量与成本快照;缺少有效用量或结算失败时保留待核对状态,不会按零 token 完成结算。请求参数没有对应价格档位时返回 PRICING_NO_MATCH。

视频素材可在客户端读取实际上传文件的大小、时长作提前提示;客户端提供的时长不替代服务端校验。 仅配置外部长任务通道且按 token 计费的模型,在任务领取时由 CommonSrv 检查素材, 创建成功不代表素材已通过检查。其他模型保留创建时预检,以满足计费需要。 检查先读取最多 64 KB 文件头,时长不足才读取最多 256 KB 文件尾,不主动下载完整视频; 源站忽略 Range 时会取消继续读取,但已在途数据仍可能产生流量。 文件缺失、无权限、超限或无法确认必需的大小/时长时,任务失败且不调用模型,释放预扣积分; 网络超时、限流与源站 5xx 最多检查 3 次,仍失败则结束任务并释放预扣积分。请通过任务查询获取最终状态。

图片输入同样检查:图生视频首尾帧、参考图片,以及换脸/换人的目标图片。 服务端最多读取 256 KB 文件头,识别 PNG、JPEG、WebP、GIF 的实际格式和尺寸; 不执行完整图片解码或人脸质量识别。无法识别时返回 IMAGE_HEADER_UNVERIFIED,需转换格式后重试。 模型 params 可配置 max_input_image_mb、allowed_input_image_formats、 min_input_image_width、min_input_image_height、max_input_image_width、max_input_image_height;未配置的尺寸与大小限制不额外强加。 检查失败通过错误响应 hint 或任务 error 返回中文原因,领取阶段失败标记在 meta.media_validation。

图片按不同地址去重,未命中校验记录时最多并发检查 8 张。 携带 meta.input_media_ids 的本人受管图片,若已有可信封存内容版本,成功的格式、尺寸和大小记录按内容版本、输入变体和校验器版本复用,不因超过 5 分钟重复读取。 历史未封存资源仍最多复用 5 分钟;纯外链每次提交重新检查,同一次提交内相同完整地址的成功图片校验结果最多复用 5 分钟,避免提交检查与派发检查重复读取。每次仍检查链接有效期、受管资源归属和状态,并应用当前模型限制;变换参数、内容版本变化或缓存失效时补验。资产数量与 HTTP 记录数量分别统计,一条 HTTP 记录不代表新增了一个输入资产。 文件存在性按需核对:页面解析本次资源或生成任务实际使用资源时才检查,不扫描其他用户或未访问的历史资源。404 表示已缺失,403、超时和 5xx 表示暂时无法确认。 默认申请 6 小时下载链接。受管图片在真正派发及队列领取前按模型取图窗口检查;可通过模型 params.input_fetch_window_seconds 指定窗口,否则采用 max_execution_seconds 或通道执行超时,另留默认 10 分钟余量。 图片的实际签名、临时凭据或任务保护期限更短时,以较短者为准;不足时返回 IMAGE_URL_LIFETIME_TOO_SHORT,不会调用上游。视频理解在派发边界重新签名并立即执行真实 Range 探测,不要求视频下载链接覆盖完整生成周期,也不会返回图片专用的有效期错误码。已知过期的纯图片外链仍返回 IMAGE_URL_EXPIRED。 成功预检不保证文件以后不会被存储管理员删除;换链接不能恢复已删除的文件。

时时科技异步视频通道创建任务时,若服务端确认连接尚未建立(DNS 失败或连接超时), 可使用同一个幂等任务标识最多尝试 3 次; 仍失败时任务以确定失败结束并自动释放全部预扣积分。已建立连接后的读取超时、HTTP 5xx 或其他无法确认上游是否受理的错误不会自动重发,以避免上游重复计费;此类任务保留为结果不确定,等待对账处理。

视频超分

视频生成通道可设置 config.supports_post_upscale,缺省为 true。应用在生成完成后可查询原任务的 GET /ai/jobs/:id;其 job.post_upscale 返回 supported、source_media_asset_id、source_model_key 和组合任务的 pipeline_mode。独立超分应提交原始素材的 media_asset_id、原视频时长 duration(秒)与显式 resolution,每次创建独立任务和计费;原始素材不存在时停止提交。

video-upscale 是独立的视频超分 Model Key,通过异步任务接口调用。腾讯 MPS 提交使用平台生成且持久化的 SessionId;若 ProcessMedia 的响应在返回 TaskId 前中断,任务进入只读对账状态,通过 DescribeTasks 与 DescribeTaskDetail 找回原任务,绝不重新提交付费处理。明确的 CAM 鉴权拒绝会标记为未提交且停止自动重试;对账窗口耗尽后才暂停等待人工核查。 传入 input.video_url,目标分辨率用 resolution 选择 480p、720p、1080p、2k、4k、8k、数字p、source 或 custom。2k/4k 分别表示短边 1440/2160; source 保持源尺寸;custom 使用 width/height 或 target_short_edge/target_long_edge,两组参数互斥。resolution 必须显式传入;自定义尺寸也要传 resolution="custom"。 缺少分辨率会在计价和供应商调用前返回错误。此模型不使用 size 字段。 可选 super_resolution_type = lq | hq(低清噪声/高清素材),未传沿用通道默认; 可选 super_resolution_scale = 2 对应腾讯 Size 字段,不能传入平台未声明的其他倍数。 输出为 MP4 文件;codec 支持 h264/h265/h266/av1/mv-hevc。常规编码显式边长范围为 0 或 128–4096, MV-HEVC 扩展到 7680 且要求多视角源;8k 不代表任意编码都支持,超出对应范围返回 INVALID_INPUT。 默认 H.264、6000kbps、源帧率、AAC 音频;这些默认值可由本次请求覆盖。不包含 AI 插帧或大模型增强。 超分补出的细节不保证与原始场景完全一致。

受理后返回 CommonSrv 任务 ID,按通用任务接口查询状态与结果;腾讯任务结束不代表超分成功, 必须取得成功的转码子任务及输出文件。结果进入通用媒体回存流程,遵循相同的任务完成与回调契约。 模型与通道初始禁用,管理员需配置腾讯凭据、备用腾讯 COS 存储通道、积分价格并完成实测后启用。 腾讯云凭据统一为同一对 SecretId 与 SecretKey,支持通过环境变量引用; 调用方仍使用平台 API Key,公开请求格式和认证方式不变。服务端认证配置缺失或无效时请求会失败, 不会使用已停用的历史认证配置。 SecretId 所属身份需要 MPS 的 ProcessMedia、DescribeTasks、DescribeTaskDetail 和 DescribeMediaMetaData 权限;这与 MPS 读取和写入 COS 所用的 MPS_QcsRole 服务角色是两项独立授权。主账号可在 MPS 控制台概览页执行“COS 授权”, 授权完成后可在访问管理的角色列表核对 MPS_QcsRole。 MPS 通道不重复保存 Bucket、COS 地域或 COS 密钥:运行时优先复用项目当前的腾讯 COS 或 CloudBase 写入通道; CloudBase 会解析为其底层 COS,MPS 直接读取和输出,并将结果登记回同一 CloudBase 环境,不再复制到另一存储桶。 项目使用其他存储时,改用管理员选择的备用腾讯 COS 或 CloudBase 通道。Bucket、地域和凭据均来自该存储通道, MPS 的 API 地域只决定任务提交位置。受管素材若能验证为腾讯 COS 对象,会用原生 COS 输入提交,避免按普通外链取回。 服务端在扣费前通过腾讯 DescribeMediaMetaData 核验尺寸、帧率和时长;无法核验返回 VIDEO_METADATA_UNVERIFIED, 不信任客户端传入的计价维度。参考成本按输出短边、帧率 ≤30/≤60/≤120 和编码计入超分费及普通转码费, 自定义及源尺寸也按实际规格匹配。无价格档位时返回 PRICING_NO_MATCH,不会按1080P或30帧兜底。 上海、广州等中国大陆地域使用同一档内置参考成本;海外地域不会自动套用大陆价。 参考价仅供配置,以账户合同及地域为准;平台积分以管理员配置价格为准,源视频时长用于提交计价。

const { job_id } = await CommonSrv.ai.submit("video-upscale", {
  video_url: "https://example.com/source.mp4", duration: 5, resolution: "4k",
  super_resolution_type: "lq", codec: "h265", fps: 60, video_bitrate: 12000
});
const result = await CommonSrv.ai.wait(job_id);

视频超分增强

video-upscale-enhance 是独立的视频超分增强 Model Key,与普通超分分别选用、分别配置价格。 输入同样使用 video_url 和上述可配置尺寸、编码及音频参数, 可选 operation = "enhance" 及 strength:weak、normal、strong,默认采用通道的enhance_strength(未设置时为 normal);无效强度返回 INVALID_INPUT。 腾讯通道仅开启 Diffusion 大模型增强,不同时开启普通超分、降噪或插帧。 输出尺寸、音频、任务查询和回存规则与视频超分相同。增强模型不接受 super_resolution_type/scale, 普通超分不接受 strength,避免普通超分与大模型增强被静默混用。增强可能改变人脸、文字或纹理细节,先用短样片对比效果。 此模型与通道初始禁用,需独立配置价格并实测后启用。

const { job_id } = await CommonSrv.ai.submit("video-upscale-enhance", {
  video_url: "https://example.com/source.mp4", duration: 5,
  resolution: "1080p", strength: "normal"
});
const result = await CommonSrv.ai.wait(job_id);

视频生成 2.5 超分 / 超分增强(透明组合)

video-2.5-cf 对应用表现为一个视频模型。应用提交提示词、参考图、时长、比例和最终resolution;平台内部先生成视频,再执行超分,最终只返回超分后的视频。 参考图既可放在 OpenAI 兼容的 content 多模态数组中,也可使用first_frame、last_frame 或 reference_images;这些字段会原样进入第一步 Seedance 2.5 生成,不会交给超分步骤。 需要腾讯 DiffusionEnhance 大模型增强时,改用独立的 video-2.5-cf-enhance; 它的公开输入、分辨率选择、任务状态和结果结构与 video-2.5-cf 相同,内部第二步固定选择video-upscale-enhance,不会与普通超分静默互换。resolution 是必填计费参数;应用应从模型目录的 params.resolutions 读取管理员开放的选项, 不要在客户端写死分辨率列表。缺少或不在开放范围内时,任务在预授权和调用供应商前报错。

用户价格按本次分辨率实际命中的生成步骤价格与超分步骤价格相加,再乘组合售价倍率,只预授权和结算一次。 因此同一时长下 720p、1080p、2k、4k 可以显示不同总价;应用不会看到两笔独立订单。

const model = (await CommonSrv.ai.models()).items
  .find((item) => item.model_key === "video-2.5-cf");
const supported = model?.params?.resolutions ?? [];

const { job_id } = await CommonSrv.ai.submit("video-2.5-cf", {
  prompt: "电影感海边日落,人物向镜头走来,无字幕",
  content: [{ type: "image_url", image_url: { url: referenceImageUrl }, role: "first_frame" }],
  duration: 5,
  ratio: "16:9",
  resolution: supported.includes("2k") ? "2k" : "1080p"
});
const result = await CommonSrv.ai.wait(job_id);
const enhancedModel = (await CommonSrv.ai.models()).items
  .find((item) => item.model_key === "video-2.5-cf-enhance");

const { job_id } = await CommonSrv.ai.submit("video-2.5-cf-enhance", {
  prompt: "电影感人像近景,保留自然皮肤纹理,无字幕",
  duration: 5,
  ratio: "16:9",
  resolution: enhancedModel?.params?.resolutions?.includes("2k") ? "2k" : "1080p"
});
const result = await CommonSrv.ai.wait(job_id);
超分 / 超分增强:完整参数参考

应用端通过模型目录 input_schema 获取字段定义,通过 params.resolutions 获取通道开放范围。 fps=0 为随源;非零 fps/fps_denominator 必须在 0–120 以内,例如 30000/1001。 resolution_adaptive=open 表示长短边,close 表示宽高。宽高为0时按原比例推算,两个都为0时保持源尺寸。 remove_audio=true 去音轨;audio_codec=copy 复制音轨。编码、源音频和采样率必须兼容。 这些模型交付单个MP4视频,不接受用 container 切换为HLS分片或纯音频;其他增强功能不混入这两个Model。 计价字段 billing_resolution、billing_fps_tier、billing_codec 由服务端核验结果覆盖,调用方无需传入。

字段说明
video_url输入视频地址;素材读取按现有授权规则校验
resolution必填。预设、任意数字p、source(原尺寸)或 custom(自定义);2k/4k 对应短边1440/2160。8k须满足编码尺寸限制
width宽度;自适应开启时表示长边。0按比例计算
height高度;自适应开启时表示短边。非零边长至少128,常规编码上限4096,MV-HEVC为7680
target_short_edge目标短边,和width/height互斥
target_long_edge目标长边,和width/height互斥
resolution_adaptiveopen=长短边;close=宽高。预设及长短边要求open,width/height自定义默认close;可选 ["open","close"]
codecMP4可用视频编码;MV-HEVC要求多视角视频;可选 ["h264","h265","h266","av1","mv-hevc"];默认 "h264"
fps目标帧率分子;0随源;fps/fps_denominator不得超过120,变帧率不等于AI插帧;默认 0
fps_denominator帧率分母,支持30000/1001等分数帧率;默认 1
video_bitrate视频码率kbps;0随源,非零128–100000;默认 6000
fill_type宽高比变化时的填充方式;可选 ["stretch","black","white","gauss","smarttailor"]
gop关键帧间隔;0自动
gop_unit关键帧间隔单位;可选 ["frame","second"]
rate_control码率控制模式;可选 ["VBR","ABR","CBR","VCRF"]
vcrf质量因子;较小值提高质量
video_profile仅h264;可选 ["default","baseline","main","high"]
video_levelh264/h265编码级别;空字符串自动,具体枚举按腾讯文档及编码校验
bframesB帧数
bit_depth编码位深;可选 [8,10]
sar显示高宽比;可选 ["default","1:1","2:1"]
no_scenecut自适应I帧开关
raw_pts保留源时间戳
compress按源视频码率压缩的比例
stereo3d_type仅MV-HEVC;按拆分后的单视角分辨率计价;可选 ["side_by_side","top_bottom"]
remove_audio去除音频;默认 false
audio_codec音频编码或直接复制;复制要求源音频兼容MP4;可选 ["aac","mp3","mp2","copy"];默认 "aac"
audio_bitrate音频码率kbps;0随源,非零26–256;默认 128
audio_sample_rate音频采样率Hz;0随源,合法值取决于音频编码;默认 48000
audio_channels0随源,1单声道,2双声道,6为5.1声道;可选 [0,1,2,6];默认 2
super_resolution_type普通超分:低清噪声素材lq、高清素材hq;未传时采用通道默认值;可选 ["lq","hq"]
super_resolution_scale腾讯SuperResolution.Size当前仅声明2倍;输出尺寸仍由resolution/宽高控制;可选 [2]
strength大模型增强强度;未传时采用通道默认值;可选 ["weak","normal","strong"]

腾讯MPS参数定义 · 腾讯MPS计价说明

列出启用模型

视频任务受理后会尝试唤醒已授权的维护 Worker。时时科技创建响应丢失时, 服务端使用生成前持久化的 client_task_id,只读查找最多 500 条平台原任务; 找到同模型的唯一匹配后继续查询原任务,不重发生成。未找到、匹配不明确或查找失败时仍转人工核查。 平台明确拒绝创建(例如模型维护)不会进入此恢复流程。

时时科技视频任务查询的 HTTP 错误、响应读取失败或无效响应不会被当作生成失败; 已取得供应商任务 ID 的任务保留原 ID,由任务维护机制退避重试查询,连续失败达到上限后暂停待审核。 创建响应丢失且未取得供应商任务 ID 时,需先核对并找回原任务,查询重试不会重新提交生成请求。

图像和视频模型的 params.resolutions 来自已启用模型通道的分辨率能力并集; 视频模型的 params.duration_min / params.duration_max 同样从已启用通道聚合。 这些能力在通道配置中维护;按分辨率分档计价时,保存会校验能力列表与价格档位一致。 分档价格必须明确匹配所有计价维度,未匹配返回 HTTP 400、PRICING_NO_MATCH, 在积分预授权和上游生成前拒绝请求,不套用兜底或最后一档价格;明确统一按张或按次收费仍可使用统一价格。 应用每次打开模型设置需重新读取目录,展示本地选项缺少或多出的分辨率以及时长范围差异,修正后才保存启用配置。 组合模型在目录中返回 model_kind="pipeline" 以及管理员配置的 input_schema、output_schema;应用仍只提交一个 model_key 和一份 input,不需要知道内部使用了哪些生成、超分、转码或其他步骤。管理员调整流程并保存后,目录文档随该模型协议一起更新;内部固定参数不会出现在公开文档中。 组合步骤返回已存储的产物时,平台完成资产登记后重新核对资源状态,再提供读取地址;资源仍在处理时保持等待,资源缺失、失败或已删除时不返回读取地址。已保存的原对象与登记回执会保留,读取地址生成失败后的存储恢复复用原结果,不需要重新上传或生成。步骤结果尚未完成存储收尾时,不代表整个组合任务已完成交付。 组合任务按各步骤当次匹配价格求和,再乘组合售价倍率并统一向上取整,只预授权和结算一次。平台在父任务内保存每一步的输入、结果、供应商任务号与计费判断:前一步成功而后一步失败时会保留前一步结果,只推进失败步骤。只有连接前失败、明确拒绝或限流等可证明供应商未受理的错误才有限次自动重试;提交结果不确定、供应商任务已创建或自动重试用尽时会暂停并等待管理员核查,不会因查询、恢复或刷新再次扣用户积分。管理员创建步骤重试时按原任务账务事实自动确定承担方:原任务已经产生用户扣款时由平台承担;尚未扣费时只按该步骤冻结的价格预授权,供应商受理后结算,明确未提交时释放。 步骤之间需要移动图片、视频或音频时,平台先通过持久化文件转存任务保存中间产物,再开始下一付费步骤;浏览器可用时优先协助传输,页面关闭或不支持跨域时由 Worker 接管。转存完成会立即唤醒父任务。组合通道明确设置中间文件过期小时数后,仅非最终步骤新生成的资源进入平台临时对象目录;未配置的新任务按普通用户资源保存并占用用户空间。组合任务全部完成后仍立即删除配置为临时的中间资源,对象存储生命周期为删除失败的兜底。转存期间父任务继续保持处理中,应用不需要读取内部文件任务,也不需要重新提交组合模型。 公开任务查询只读持久快照,不查询供应商、不续执行租约。供应商查询由统一协调器的到期推进和 Worker 完成,均受数据库维护租约与 next_poll_at 约束。响应 retry_after_ms 与 Retry-After 提供观察建议;手动刷新不会提前执行供应商重试。任务日志按持久 task_id 区分,trace_id 只关联一次请求;合并请求的摘要与其中每个任务的执行日志分别记录,不能用共同 trace_id 推断任务归属。

const { items } = await CommonSrv.ai.models();
const { items: videoModels } = await CommonSrv.ai.models(CommonSrv.ai.CATEGORIES.VIDEO);
// items[i]: { model_key, display_name, category, async, params, test_fixture }
//   - category: "llm" | "image" | "video" | "tts" | "stt"
//               推荐用常量:CommonSrv.ai.CATEGORIES.LLM / .IMAGE / .VIDEO / .TTS / .STT
//   - async  : true  → 必须走 CommonSrv.ai.submit + .wait
//              false → 走 CommonSrv.ai(...) 同步调用
//
// 分流规则(服务端强制,靠 async 字段判断即可,别自己猜):
//   if (m.async) { await CommonSrv.ai.submit(m.model_key, input); ... }
//   else         { await CommonSrv.ai(m.model_key, input); }
✅ ai() / ai.submit() 的 input 已标准化,各 category 采用 OpenAI 兼容协议为基座,adapter 内部翻译到各家上游。业务方按下方 std schema 传入即可,不用关心通道差异。
  • LLM(OpenAI /chat/completions 基座):{ messages:[{role,content}], temperature?, max_tokens?, top_p?, response_format?, thinking?, reasoning_effort?, frequency_penalty?, presence_penalty?, stop?, system_prompt?, timeout_ms? };content 支持多模态数组(text / image_url / input_audio / file)。
  • Image(OpenAI /images/generations 基座):{ prompt, n?, size?, response_format?, quality?, style?, image?, mask?, seed?, negative_prompt?, raw? }。
  • GPT Image 2(Stargo):model 填 gpt-image-2,通过图像 job 接口调用;上游同步出图。 单次 n=1,固定 URL 返回;size 支持 1024x1024、1536x1024、1024x1536, 比例字符串映射为方/横/竖标准尺寸(横图不是精确 16:9)。 image 支持一张 HTTPS 或 image data URL 参考图,服务端转为 multipart 文件提交 edits;不支持 mask。 上游超时默认 180 秒,结果通过统一媒体存储流程转存;HTTP 200 中的 error 同样视为失败。
  • Video(OpenAI Sora /videos 基座 + 扩展):{ prompt, seconds?, size?, first_frame?, last_frame?, reference_images?, reference_audio_urls?, reference_video_urls?, seed?, generate_audio?, negative_prompt?, raw? }。duration 是 seconds 的 alias。万相 3.0 同样使用这套输入; 文生、首帧、首尾帧与参考生视频由素材字段自动适配,不要在业务端拼阿里云协议。
  • TTS(OpenAI /audio/speech 基座 + 扩展):{ input, voice?, response_format?, speed?, language?, pitch?, volume?, emotion?, raw? }。text 是 input 的 alias。
  • STT(OpenAI /audio/transcriptions 基座):{ audio, language?, response_format?, temperature?, prompt?, diarization?, raw? }。

size 字段三种写法:"1024x1792"(精确 W×H,OpenAI 原生)· "16:9"(只定比例,adapter 用 1080p 默认档)· "16:9@1080p"(比例 + 档位,档位枚举 720p / 1080p / 2k / 4k)。adapter 会翻译成上游需要的形态。

raw:{} 逃生舱:需要上游私有字段(例如 seedance 的 camera_fixed、minimax 的 subject_reference)时可直接透传,会在标准字段翻译后 shallow-merge 到 upstream body(覆盖)。

LLM 参数合并与推理规则
  • 优先级为:Model Key 默认参数 → 本次请求参数 → 通道协议适配。业务只传统一参数,不要自行拼接供应商字段。
  • deepseek-v4-pro、genllm、genlongtext 默认关闭推理。实体抽取、分类、翻译和严格 JSON 输出通常保持关闭;创意策划、复杂推演和长篇创作等业务才显式传 thinking: { type: "enabled" },并可配 reasoning_effort: "high"。
  • 通道会把统一参数转换为供应商协议并移除不支持或互斥的字段;模型推理过程不会作为面向用户的业务结果返回。

同步(LLM · 直接返回结果)

const reply = await CommonSrv.ai("google/gemini-2.5-flash", {
  messages: [{ role: "user", content: "hi" }],
  // params: { max_tokens: 2000, thinking: { type: "disabled" } },
}, {
  meta: { feature: "文本推理" },
  ref: { type: "your_app", id: "chat-1234" },
});
// reply: { ok, text, usage: { input_tokens, output_tokens }, credits_consumed, balance_after }
⚠️ 同步 / 异步是按模型 + 通道配置的,客户端只看 ai.models() 返回的 async 字段分流:
  • async=true 的模型走 CommonSrv.ai(...) → 抛 USE_JOBS_ENDPOINT,必须改用 .submit + .wait。
  • async=false 的模型走 .submit → 抛 USE_INVOKE_ENDPOINT,必须改用 CommonSrv.ai(...)。
  • 不要写「同步失败自动 fallback 到异步」的兜底逻辑。

异步(LLM 长任务 / 图像 / 视频 / TTS / STT)

持久队列:POST /api/public/v1/ai/jobs 先入队,返回 HTTP 202、稳定的 job_id 和 pending;这是提交成功,不是要求重新生成。浏览器协调器调用 tasks/advance 领取并推进任务,Worker 共享相同队列并在新任务入队 60 秒后兜底。关闭所有浏览器且停用 Worker 会暂停执行,但不会删除已入队请求。

批量提交使用 POST /api/public/v1/ai/batches,请求为 {key,title,requests:[...]};requests 为 1–100 条 /ai/jobs 请求,每条必须有独立 idempotency_key。整批事务入库后返回 202、batch_id 与 jobs(job_id、idempotency_key、status)。相同 key 必须携带相同标题及完整请求清单,否则返回 IDEMPOTENCY_CONFLICT;响应丢失时原样重试。任务快照含批次父任务和每条子任务,父任务 business_ref 包含 total、queued、running、succeeded、failed、needs_review、remaining,禁止用正在执行的数量代替整批剩余量。单项或批量请求可在每条任务的 meta.display_title 提供 1–200 字的展示标题,固化到该任务的 business_ref.title;该字段只供任务界面展示,不参与身份、幂等、计费或调度。

在 CommonSrv「统一任务消费者 → 生成任务并发」配置全局默认及项目覆盖。同一项目内每位用户的单条、批量、多个浏览器和 Worker 共用总额度(默认 3),还可按模型管理 category 设置分类上限。分类在入队时固定;组合模型内部步骤不重复计数。主任务持续占用至完成、失败、终止或暂停待人工处理,轮询和回调不单独占生成额度。文件转存有独立的 I/O 并发设置。入队同时取得受管输入保护,调用方随后关闭准备批次不会释放已排队任务的输入。

异步提交前,请先保存应用自己的素材版本与稳定的 idempotency_key。正常流程只提交一次,取得 job_id 后仅通过 GET /ai/jobs/:id 查询;只有首次响应丢失、尚未取得任务 ID 时才使用同一幂等键恢复。查询响应会回传提交时的 ref_type 与 ref_id,业务端应校验它们再绑定结果。同一请求恢复会返回原 job_id;相同键但不同请求返回 IDEMPOTENCY_CONFLICT。

页面等待结束不代表生成失败。GET /ai/jobs/:id 与 CommonSrv.ai.get/wait 只读原任务。应用根节点创建 const tasks = await CommonSrv.tasks.coordinate(); tasks.start();,退出或卸载调用 tasks.reset()。页面通过 tasks.registerSource 注册观察源,统一协调器维护各源的到期时间、独立请求和失败退避。同源页面可用 CommonSrv.tasks.shareRead(read, { resourceKey, intervalMs, version? }) 包装只读资源,再由 registerSource 调用其 read(signal)。每个标签先用自己的授权取得基线;之后只广播 SHA-256 版本摘要与新鲜度,不广播业务正文、令牌或签名链接。资源键必须覆盖所选实体范围,显式变更调用 invalidate(),卸载调用 close()。遇到同范围在途读取最多等待 1000 ms 复用结果;持锁标签冻结时独立读取,不依赖长寿命主标签或额外心跳。任务同步返回 retry_after_ms 与 next_advance_after_ms;后者为 null 时没有浏览器到期待办,不发送空推进。初始同步按每页最多 200 条返回未完成任务和其父批次;初始及增量 cursor 都是不透明字符串,须原样回传至 has_more=false 后发布完整快照,分页期间的变化会继续追赶。同源、同项目客户端、同用户的标签页共享观察快照;浏览器不支持共享或共享锁不可用时,退回独立只读查询,执行仍由服务端租约保护。正常查询档位为短任务 15 秒、默认 30 秒、视频及长任务 60 秒、腾讯视频理解 120 秒;动态通道 poll_interval_ms 优先。Worker 流式文本不增加模型状态查询,页面观察 60 秒。空闲发现 120 秒、结果收尾观察 15 秒,已持久化的阶段重试时间不会被普通间隔覆盖。任务推进、恢复与观察各自执行,慢恢复不阻塞到期工作。关闭浏览器后由已授权 Worker 继续处理。

启用 meta.persist_output_media 的任务,在供应商完成但存储未完成时返回 running、进度 99;不会把临时下载/上传错误当作生成失败。存储流程保存目标位置、分片进度和上传成功回执;已有回执时只核验原对象元数据并完成登记,不重新下载、上传或生成。图片展示链路可能自动压缩,不能用展示下载大小校验原文件。查询响应附带 generation_status(原生成状态)、delivery_status(generating、persisting、binding、needs_review、delivered、terminated、failed)、polling_paused、maintenance_stage 与 next_poll_at。客户端在 polling_paused: true 时停止执行等待及转圈,但 CommonSrv.tasks.observe() 继续观察人工恢复与终止;返回页面或收到转存状态变化通知时,可针对已有任务做一次状态同步以反映管理员恢复、终止和已完成交付,不能重新提交生成任务。正常定时查询不应早于 next_poll_at。needs_review 表示自动处理已暂停,并非供应商判定生成失败。管理员核对错误后可恢复原结果转存,或终止结果收取;恢复不会重新生成或再次预扣。终止保留生成状态和费用证据,对外兼容返回 status: failed、delivery_status: terminated、output: null、next_poll_at: null,内容代理返回 DELIVERY_TERMINATED。终止撤销后续领取和完成回报资格,已经发出的网络请求可能继续至超时,但不能重新登记为成功;仍有未退扣款的任务进入退费审核,终止本身不自动退款。管理队列展示生成完成时间和转存状态,不按生成后的时长推断转存失败。管理员预览会优先按任务、项目、用户和输出位置读取已转存资产,并获取新的读取地址;未转存时才使用原结果链接,不接受客户端指定任意预览 URL。可预览原结果后手动重新排队原转存,或终止交付并进入现有退费审核;人工重新排队不重新生成,可覆盖普通重试 / 浏览器优先等待,但不会抢占有效传输租约,并等待旧上传票据安全到期。管理员批准退费后才产生退款流水,重复审核不能重复退回。已有供应商生成失败的自动退款规则保持不变。

业务关联在提交时指定不可变的 meta.delivery_binding={entity_type,entity_id,slot?,result_kind?}。媒体默认 result_kind=media,需启用 persist_output_media;业务服务写入资源关联后通过 /storage/references 发件箱提交回执。文本/JSON 使用 result_kind=text,不启用 persist_output_media:签名回调触发服务端批量读取 POST /api/public/v1/tasks/results,按原任务 ID、用户、实体、creation_key 原子应用业务结果和持久回执,再用项目密钥调用 POST /api/public/v1/tasks/ack,提交 receipts 数组(task_id、app_user_id、entity_type、entity_id、result_version)。接口逐项返回 confirmed;版本来自 results,目标、所有者、结果版本不匹配或已终止时不确认。浏览器不能提交文本回执。结果应用失败保持待处理;恢复重放原结果和回执,不重新生成。任务只有经过交付核对才成为 succeeded。

文件传输列表同时返回 browser_capable、worker_not_before、next_attempt_at 和 cancelled 状态。worker_not_before 仅表示最早可领取时间,不表示 worker 已启动;需要停止展示的 cancelled 任务不能继续画运行中动画。领取响应的 source.expires_at 表示原地址的已知到期时间;浏览器和 worker 必须先核验 destination.existing_check 指向的原目标文件,已有完整对象则直接完成登记。Mock 结果引用的是后台配置的固定样本,临时读取地址到期前由 CommonSrv 自动续签并恢复队列,不需要人工处理,也不会重新调用模型。真实供应商结果无法安全续签且目标缺失时,才报告 FILE_TRANSFER_SOURCE_EXPIRED 并暂停等待管理员处理。最后一个生成结果完成转存时,CommonSrv 会在同一数据库事务中把无需业务关联的父任务标记为完成;配置了业务关联的父任务则进入 binding,接入方不应继续把已经完成转存的任务显示为“转存中”。

模型结果存储使用 CommonSrv 的持久化文件传输队列。已登录应用可通过 GET /api/public/v1/file-transfers 单独查看本人任务;站点根级协调器通过同一路径的 POST 原子领取浏览器任务,响应返回 task、trace_id 和服务端浏览器消费者配置。共享 SDK 领取显式传 include_transfers: false,不额外查询转存历史。任务中心通过 CommonSrv.tasks.observe() 的统一任务同步恢复状态,刷新、换设备或长期未打开页面都从服务器恢复,不依赖浏览器保存任务 ID。协调器只广播当前执行进度,完成或交接后移除本地进度,以服务器任务状态为准。SDK 在空队列后停止连续领取;统一任务同步发现排队的转存子任务,以及页面聚焦、恢复联网和显式刷新时会唤醒领取。心跳、传输重试和接口退避由“系统配置 → 任务消费者 → 浏览器转存消费者”统一下发。一个登录账号在同一浏览器的转存并发由 max_concurrent_transfers 控制,默认 5,可配置为 10 或更高的正整数;跨标签页协调,跨浏览器由数据库领取约束共同限制。transfer_enabled 以服务端已保存的有效配置为准;无效配置不允许新领取,关闭后已领取的任务仍可完成。普通成功查询、成功空领取与成功文件心跳默认不写逐条调用明细;失败、实际领取及状态变化仍记录。浏览器在开始下载和开始上传时分别上报阶段开始事件;成功完成或失败仍上报结果,调用日志按客户端阶段时间展示,同时保留服务器接收时间。POST 必须携带本次领取生成的 UUID claim_token,网络结果不确定时以相同 token 重试。取得任务后,客户端必须通过 X-File-Transfer-Trace-Id 把响应的 trace ID 传给下载、上传、心跳、完成或交接上报;项目的浏览器来源需允许该请求头通过 CORS 预检。领取结果只包含短期、精确对象的源读取和目标写入能力;重试用尽、CORS、网络或页面离开时调用 /abandon 交给统一任务运行器中的转存适配器。上传后调用 /complete;所有写回必须原样携带 attempt、token 和服务端返回的 file_id。服务端重新核验目标大小、类型、位置和可读性后才将任务置为 ready。完成提交会在当前 EdgeOne 请求的后台上下文中直接调用同一套带数据库租约的任务状态机;组合模型可立即进入下一阶段,不额外调用云函数领取任务。直接推进异常时才请求外部 Worker 补漏,定时维护仍负责进程中断等未完成情况。重复 complete、多标签页及 Worker 竞争同一个任务时,只有取得维护租约的执行者可以推进或提交下一阶段。完成确认返回 409 FILE_TRANSFER_LEASE_LOST 表示当前领取已失效,客户端停止使用旧 token;对象大小、类型和文件签名不匹配分别返回 FILE_TRANSFER_MEDIA_UPLOAD_SIZE_MISMATCH、FILE_TRANSFER_MEDIA_UPLOAD_CONTENT_TYPE_MISMATCH、FILE_TRANSFER_MEDIA_UPLOAD_SIGNATURE_MISMATCH。核验网络失败、数据库提交或心跳基础设施错误返回 503 与相应的 FILE_TRANSFER_VERIFY_FAILED、FILE_TRANSFER_COMMIT_FAILED、FILE_TRANSFER_HEARTBEAT_FAILED;这些错误不代表模型生成失败。

浏览器消费者配置集中管理文件大小上限(默认 128 MB,可填 1–512 MB;这是浏览器消费者配置范围,超出配置值的文件交给后台)、无人领取时后台接管等待(默认 15 秒,配置项 priority_window_ms 以毫秒保存)、源链接到期前接管余量(默认 15 分钟)和单次下载/上传超时(默认 10 分钟)。前三项在新转存任务入队时生效,网络超时在下次浏览器领取时生效,既有有效租约不变。浏览器转存使用用户 JWT,独立于云端 Worker 权限。浏览器优先窗口内的新转存不检查运行器配置、不触发转存唤醒;只有已允许后台领取的新转存才触发唤醒。窗口到期由维护/定时触发补漏;浏览器已领取但失去租约时,在旧上传票据失效并经过 5 秒安全余量后允许后台接管,不再等待剩余浏览器优先窗口。核验与完成提交期间 SDK 持续续租;提交成功后清空租约导致的心跳冲突不会把成功误判为失败。不存在后台消费者只影响后台接管,不阻止符合条件的浏览器转存。生成阶段的云端执行权限仍按生成任务自身要求独立检查。上游链接临近到期时会提前允许 worker 接管。创建任务时还会探测来源是否明确返回浏览器 CORS 授权;没有授权、私有来源或超过浏览器策略上限的文件只允许 worker 领取,避免浏览器执行一次必然失败的下载。浏览器和 worker 竞争同一数据库租约;显式 tasks.advance 命令可以受限推进已受理的供应商轮询任务、组合模型的服务端阶段状态机及结果登记,文件传输仍遵守既有转存流程和传输租约。组合模型由浏览器显式 tasks.advance 命令推进时,供应商调用仍发生在 CommonSrv 服务端,并继续使用父任务维护租约、阶段提交检查点和原子状态转换防重;Worker 定时维护只负责无人在线或事件丢失时补漏。worker 接管前检查固定目标对象,已完整存在时直接完成核验;浏览器明确放弃后还会等待旧写入票据失效,避免并行 PUT。

生成成功后的 Job 维护查询还会检查既有转存队列:对 queued / retry、next_attempt_at 与 worker_not_before 均已到期且源链接未过期的记录,尝试补发 Worker 唤醒。唤醒仍由数据库统一限制冷却时间、权限与容量,不抢占已领取的传输,不绕过浏览器优先窗口,不重新生成视频。唤醒请求发出不等于 Worker 已领取,需结合领取记录和唤醒调用日志核对。

管理端将“执行方式”和“触发来源”分开显示。云函数聊天任务仍只由原 openai-chat-fc Worker 认领;openai-chat-job 保留专用执行机制。换人/换脸属于多阶段流程,若某阶段调用结果不明,暂停等待核对,不自动重复提交阶段。

平台运维:所有 Worker Hook 只接受 HMAC v3,签名绑定通道 ID、用途、HTTP 方法、请求路径、环境地址、时间戳、nonce 和原始请求体,nonce 在数据库中防重放。运行器必须配置 config.worker 的 audience、concurrency 和 grants。每条 grant 明确 actions,以及 all_projects 或 project_ids;一个云函数可以领取多个授权项目,无需按项目重复部署。项目 API Key 不能代替 Worker 密钥。长任务心跳和报告必须带领取返回的 token、attempt;终止、权限撤销或租约失效后返回 TASK_LEASE_LOST 等错误,消费者必须停止。租约由心跳延长;转存重领同时等待旧上传票据失效,并先检查原目标。失去租约的旧执行方不能提交完成结果,但已经发出的网络请求未必立即结束。已发给供应商的生成请求不会因租约过期自动再次提交。

渠道的上游超时是单次 HTTP 请求的最长等待上限,不作为领取前预计耗时。Worker 在当前运行时间不足时停止领取新任务;已提交的请求如发生网络中断、超时或租约失效,供应商结果可能未知,任务进入 needs_review,保留预扣等待人工核对,不自动再次生成或退款。仍在排队或执行的任务不会仅因创建时间、渠道超时或业务端等待时长而被判失败。

浏览器上传收到目标 HTTP 400、401 或 403 时,不再用同一票据重试,向 CommonSrv 交接转存;已确认租约的有效期内未能续租时,浏览器停止当前上传。交接后的 Worker 必须等待旧上传票据到期并经过数据库现行 5 秒等待期,再核验原目标并领取。上传慢速等供应商错误可在失败诊断中查看受限的 provider_error_code,不记录供应商响应正文或签名。

统一任务 SDK(4.1):GET /api/public/v1/tasks 返回 reset、cursor、tasks 和 has_more。浏览器携带用户 JWT,仅可见本人任务;后端携带项目 X-Api-Key,仅可见本项目任务。cursor 是不透明字符串,后续查询原样回传;has_more 为 true 时继续读取。该接口不推进任务。

CommonSrv.tasks.observe() 返回观察器,支持 subscribe、onChange、getSnapshot、start、stop、reset、refresh。SDK 按任务 version 去重,空队列和 needs_review 时继续同步,断网自动退避;SDK 自动按账户隔离快照,切换账户丢弃旧请求;组件卸载时调用 reset。CommonSrv.tasks.transfers({ coordinator: tasks }) 提供共享浏览器转存协调器;CommonSrv.tasks.mount(host) 挂载共享任务浮窗,renderTasks 展示统一状态,已完成、失败、终止的任务自动从浮窗移除;needs_review 单列“待处理”,不转圈、不静默隐藏,历史和领取不受影响。浮窗可拖动、调整大小,拖到浏览器边缘或点击“收起”后缩成边缘小标签,点击标签展开;位置、大小与收纳边缘通过 storageKey 保存。旧固定设置不再锁定位置。可用 renderTasks(tasks, extraRows) 接入压缩、上传等本地阶段,复用同一个浮窗。用户提示展示中文原因和处理建议,原始错误码仅供管理端技术详情查看。后端 Node SDK 使用 cs.tasks.sync(cursor)。

总任务状态 state 为 queued、running、needs_review、succeeded、failed 或 terminated;stage 表示 generation、transfer、verification 或 delivery。generation_status 保留供应商事实。生成成功但尚未完成转存及核验时,总状态仍为 running;转存状态依持久化结果和租约判断,不按固定时长推断超时。管理端可预览原结果、恢复原结果转存或进入现有退款审核,绝不重新生成或重复扣费。

后台任务消费者:浏览器“文件转存”开关(browser_policy.transfer_enabled)控制浏览器是否领取任务;关闭后新文件直接进入后台转存,已领取的上传可完成,状态查询仍可使用。浏览器身份来自用户会话,无需配置云端运行器权限。管理后台“系统配置 → 任务消费者”只配置浏览器转存、后台生成、状态维护、文件转存和共享调度策略;项目 Webhook 不属于 Worker 权限。 单条任务回执仅核对该任务及其业务资源引用;其他任务的待处理引用不应阻塞确认。接收方必须在业务记录和关联凭据持久保存后返回 2xx,同一事件重复送达应返回成功且不重复写入。任务队列页面也可直接跳转。“共享队列与调度策略”不是 Worker。本地和云端每轮通过签名接口 POST /api/public/hooks/worker-runtime 读取同一份配置。云端只部署 task-worker 包;生成、维护和转存是包内适配器,可用 TASK_CONSUMERS 缩小某组实例的职责。资源可用性没有定期扫描或云存储事件消费者。

同一云函数可以并发执行生成、转存和维护,并服务多个已授权项目。领取额度由数据库原子检查;按项目轮转并优先保护临近过期的源文件。新增可领取转存、领取成功及维护轮次会按积压补充唤醒,数据库合并重复请求,空队列不递归触发;定时触发器继续补漏。本地 CommonSrv 使用回环 Supabase 时,唤醒固定发送到本地 Worker 的 http://127.0.0.1:43212/wake,不使用数据库中保留的云函数地址;本地 Worker 未启动时唤醒失败,不回退到云端。启动命令为 node scripts/task-worker-tools/local-daemon.js。本地与 SCF 复用统一消费者执行入口,每次触发并发执行一轮生成、维护和转存;本地仅模拟异步唤醒和固定 60 秒定时触发,不再使用消费者独立循环、空闲探测或失败后额外重试。一轮失败后等待下一次唤醒或定时触发;云端定时频率仍由平台触发器配置。消费者每轮通过签名 POST /api/public/hooks/worker-runtime 获取已保存的运行配置与权限;该接口检查维护任务租约须大于维护请求超时,维护额度租约须大于维护请求超时;不满足时返回 503 WORKER_RUNTIME_BUDGET_INVALID,消费者本轮不启动。管理页面保存与 JSON 导入执行相同检查;导入先预览并生成草稿,导出仅包含运行参数与权限,不包含签名密钥及云函数地址。该接口调用日志归为普通轮询,受“记录普通轮询”开关控制,关闭时正常读取不记录,失败仍保留。新签发的 CloudBase 素材上传与生成结果转存统一使用服务端签发的精确对象 PUT 地址,浏览器不再为素材上传登录 CloudBase;完成时 SDK 使用 completion_file_id 提交存储身份,继续核验对象大小、类型及文件内容。旧 CloudBase ticket 传输仅保留 SDK 兼容支持。创建上传意图返回 INVALID_MEDIA_INTENT 时,调用日志 meta.validation_issues 记录受限的字段路径和校验类型,不记录字段值或原始校验消息;公开响应保持原错误码。浏览器受管上传失败事件包含可用的 upload_stage(组件加载、CloudBase 鉴权或文件上传)、异常类型与白名单上传错误码,不包含票据、文件 URL 或任意云 SDK 异常消息。Worker 消费者失败汇总保留 errors 数组(stage、code、异常 type、可用的 Worker 源码 location 和 HTTP status),SCF 执行日志也输出同一安全摘要;不输出签名、票据、任意异常消息或堆栈。空队列是正常退出,不应返回消费者失败。本地与 SCF 均从统一入口开始计时,包含唤醒鉴权、模块加载与配置读取耗时;Worker 直接使用 SCF 文档提供的 time_limit_in_ms 与配置 function_timeout_ms 的较小有效值减去本次调用已耗时,不调用 getRemainingTimeInMillis。平台未提供超时字段时使用配置超时;保留领取前的剩余时间保护。Worker 在完成核验期间继续续租,完成提交成功后的迟到心跳冲突不会误报传输失败。管理后台“系统配置 → 任务消费者”的“执行租约与恢复等待”分别配置生成租约及心跳、任务状态维护、维护额度、素材持久化、调用幂等保护、素材校验、Webhook 投递、取消上传后的残留文件检查、存储清理。各用途保持独立,生成默认 180 秒且心跳 60 秒,状态维护默认 600 秒;配置保存后下次领取或成功续租读取,清理的超时判断使用当前值。生成与 Worker 转存领取返回 lease_seconds、heartbeat_interval_ms,成功心跳返回最新 lease_seconds,消费者据此判断失联。租约必须覆盖对应请求预算,心跳必须短于租约;过期执行方仍不能续租或提交,已完成幂等调用保持终态,存储提醒结果未知时不自动重发。残留文件检查租约仅防止多个检查者同时处理,不会因租约到期而自动删除文件。腾讯 MPS 视频理解任务的后台正常轮询间隔为 120 秒;其他供应商维持各自的退避规则。存储批次保护仍使用项目级存储策略。管理后台“系统配置 → 任务消费者”的文件转存租约有效期(transfer_lease_seconds)由浏览器与 Worker 转存共用,默认 60 秒,可设 30–600 秒,必须大于浏览器心跳间隔(默认 20 秒)。保存后下次领取或成功心跳读取新值,不恢复已经过期的租约;每次续租更新为服务端当前时间加有效期。领取计划返回前与浏览器开始传输时都会续租。浏览器领取结果的 heartbeat_interval_ms 来自浏览器心跳配置,Worker 领取结果使用独立的转存心跳配置。浏览器使用领取结果的 heartbeat_interval_ms 通过页面定时器周期续租。心跳请求最多等待 10 秒且不超过心跳间隔的一半,超时后释放请求占用,下一周期继续尝试。浏览器完全冻结或设备休眠仍可能使租约过期,过期执行方不能继续提交。浏览器心跳请求可带受限的 diagnostics,调用日志 meta.heartbeat_diagnostics 保留触发来源、实际间隔配置、定时器触发/跳过次数、调度延迟、上次请求耗时与页面可见状态,诊断字段不参与租约授权。浏览器 SDK 4.0.5 将成功下载和上传的诊断合并到 /file-transfers/$id/complete 的可选 diagnostics 数组(最多两项,每项 operation 为 download 或 upload、duration_ms 为非负毫秒且不超过 86400000、completed_at 为 UTC ISO 时间);服务端仍分别记录阶段日志。失败诊断继续独立上报;完成提交失败或页面离开时,尚未确认的成功诊断通过 /client-events 补报。旧版不带 diagnostics 的完成请求仍兼容。项目 webhook_excluded_task_events 可排除 task.succeeded 投递,保留 skipped 出站队列记录;默认不排除,显式 job callback_url 不受此项目设置影响。DramaCommerce(eshopdrama)排除未使用的任务成功通知,仍接收交付就绪、失败和终止事件。浏览器心跳失败会报告不含凭证的诊断事件;心跳接口对真实租约失效返回 409 FILE_TRANSFER_LEASE_LOST,对续租服务故障返回 503 FILE_TRANSFER_HEARTBEAT_FAILED。租约和旧上传票据过期并经过 5 秒安全等待后,Worker 可以在浏览器优先窗口结束前恢复任务。心跳持续续租,大视频不受初始租约长度限制;实例剩余时间不足时不再领取。

统一项目 Webhook:项目配置一个地址,按 event 分发 order.paid、task.delivery.ready、task.succeeded、task.failed、task.terminated。显式 callback_url 仅覆盖该请求。任务状态与事件 Outbox 在同一数据库事务提交;文件完成接口不等待第三方回调。浏览器协调器优先投递,Worker 到期补漏;双方领取同一条记录,不各自实现重试。同任务按事件顺序投递,重复投递保持原 id。网络失败及缺失业务回执均指数退避并加入抖动,交付就绪最多尝试 8 次,仍未确认则进入 needs_review,管理员恢复只处理原结果。HTTP 200 不是任务完成证据:CommonSrv 必须确认最终输出清单中的全部资产及匹配业务引用,才完成绑定;回包丢失但确认已提交时也不再重复绑定。

收到 task.delivery.ready 后,应用后端使用项目 X-Api-Key 调用 POST /api/public/v1/tasks/results(传该 job_id,最多 50 个)及 POST /api/public/v1/tasks/assets(ids 最多 100 个)。Node SDK 对应 cs.tasks.results(jobIds)、cs.tasks.assets(ids)。这两个接口只读本项目数据,不调用模型、不领取、不改交付状态;项目密钥和 Webhook Secret 只能放在后端。

业务适配器须按任务、绑定目标和结果身份幂等写入业务行及本地引用 Outbox,再发送 /storage/references 回执并回读权威任务状态。不能仅凭本地 succeeded/done 或回调 2xx 宣称交付完成。引用与结果无论谁先到,服务端都重复核对持久事实;组合模型只认明确选择的最终输出,内部资产不参与业务完成判断。单次绑定不重建全剧本历史。重复调用不重新生成或扣费。

多标签页通过 Web Locks 减少重复请求,不支持此 API 时仍由数据库租约防止重复执行;观察状态不能延期 Worker 接手。上传完成后同一 attempt/token 的迟到心跳返回 state: completed,其他令牌仍返回 409。SDK 5.2.2 及后续版本与后端须成套升级;批量任务面板按批次统计,一个生成结果在生成和转存期间只计为一个进行中的任务,单项生成任务展示业务方提供的名称。不保留 GET 隐式推进路径。升级 SDK 后刷新已打开的页面,确保主脚本和任务模块均使用新版本。

声明 delivery_binding 却没有可用 callback_url/项目 webhook_url、且尚无业务确认时,任务显示 needs_review(BINDING_CALLBACK_REQUIRED),不无限等待。修正配置后恢复原任务;已提交的合法业务引用仍可完成确认。公网回调使用 HTTPS;本地联调时,业务服务与 CommonSrv 均在本机运行,可使用项目允许列表中精确登记的 HTTP 回环地址(含端口),例如 http://127.0.0.1:43189/api/public/hooks/commonsrv。只配置生产回调或省略本地回调不会完成本地业务绑定。重试耗尽后的迟到合法回执可以收敛,但已终止任务不会复活。终止会跳过尚未完成的交付就绪事件,让终止通知继续投递;手动重投也必须遵守事件顺序和租约。任务引用 Outbox 随交付尝试重放,只针对当前绑定目标,不扫描整个业务历史。服务端 GET 观察仍不执行生成;项目密钥接口 POST /tasks/results 额外返回 creation_key(创建请求的 idempotency_key),业务适配器可在目标和所有者匹配后,用相同持久键补回丢失的 job_id。键不匹配必须拒绝,不能通过重新生成猜测恢复。

后台 Worker 转存先把来源顺序写入本次执行的临时文件,核对实际字节数后以定长文件对象上传,并在完成回执前清理临时文件;多张图片或视频并发时无需把每份完整素材保留在内存。临时磁盘不足报告 FILE_TRANSFER_WORKER_SPOOL_EXHAUSTED,转存仍按已有重试和待处理规则处理,不代表模型生成失败。

调用日志 meta 保留调用发生时事实;查询时附加的 current_task 带 observed_at 与任务 version,不能当作历史状态。按 task_id、event_id、attempt_id 查找重试记录;浏览器驱动投递和 Worker 队列投递只是执行来源。SDK 的 beforeAdvance(signal) 补交回调按单实例防重,与已入队任务推进独立运行;补交挂起不占用任务执行锁。沿用统一运行配置中的 180000 毫秒请求期限,超时中止并允许后续轮次重试,停止协调器时同时中止补交;调用方应将 signal 传给请求并保持补交幂等。成功领取属于执行事件,默认日志列表会保留;空领取和普通心跳仍按轮询策略降噪。批次入口的链路包含全部生成项,单项链路只包含该生成项。参考素材可达性与元数据校验继承当前消费任务的标识,归入该生成项;素材被多个任务复用时各自保留校验记录,不按文件 URL 合并,也不归入素材原始生成任务。模拟模型受理会明确标记,没有实际调用供应商时不生成虚假的出站请求。Worker 在已有签名心跳协议中可附带 diagnostic(operation: download/upload、stage: started/completed、occurred_at、duration_ms),通过当前 attempt/token/运行器租约校验后记录下载与上传阶段;不接受文件 URL 或凭据。阶段耗时与日志请求耗时分别展示。未落最终响应的日志显示 REQUEST_OUTCOME_UNKNOWN,不据此判定任务失败。

新任务类型必须同时注册业务载荷来源、阶段、消费者、超时与恢复策略;未注册的类型数据库直接拒绝。业务项目通过稳定 ref_type/ref_id 关联结果,不自行实现领取、重试或浮窗状态机。

SDK 按响应 retry_after_ms 观察当前阶段(缺少建议时默认 30 秒,网络故障指数退避上限 300 秒,429/408 可重试且有效 Retry-After 可延长等待,其他 4xx 直接返回;signal 取消立即中止等待,默认 10 分钟等待期限覆盖卡住的网络请求),也可传 callbackUrl 让服务端在终态时主动推送——不要自建 setInterval。

// 1) 提交任务
const { job_id } = await CommonSrv.ai.submit("kling-v2", {
  prompt: "a running cat", duration: 5,
}, {
  meta: { feature: "视频生成" },
  // callbackUrl: "https://your-app.com/hooks/commonsrv-ai",
});

// 2) 等待完成
const done = await CommonSrv.ai.wait(job_id, {
  onProgress: (job) => console.log(job.status, job.progress),
  // timeoutMs: 30 * 60 * 1000,   // 长任务可放宽到 30 分钟 ~ 24 小时
});
if (done.status === "succeeded") {
  // done.output 已对齐 OpenAI 协议(按 category 不同):
  //   LLM   → { id, object:"chat.completion", choices:[{ message:{ role, content } }], usage }
  //   Image → { created, data:[{ b64_json?, url?, width?, height? }] }
  //   Video → { id, object:"video", status, data:[{ url?, duration_sec? }] }
  //   TTS   → { data:[{ b64?, hex?, format, duration_sec? }] }
  //   STT   → { text, ... }
  // done.credits_consumed
}

// 3) 一次查状态(不轮询)
// const job = await CommonSrv.ai.get(job_id);

REST / OpenAI 兼容接入(无 SDK 运行时)

REST 基址为 https://<your-commonsrv-host>/api/public/v1。Bearer token 由业务登录流程获得; 服务端项目调用使用项目 API Key。同步 LLM 使用 /ai/invoke 或/chat/completions,异步 LLM、图像、视频与语音使用 /ai/jobs并查询 /ai/jobs/:id。不要自己实现 refresh token 轮换。

# 同步 LLM(统一 CommonSrv 协议)
curl -X POST https://<your-commonsrv-host>/api/public/v1/ai/invoke \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model_key":"deepseek-v4-pro",
    "messages":[{"role":"user","content":"请返回 JSON"}],
    "params":{"max_tokens":2048,"thinking":{"type":"disabled"},"response_format":{"type":"json_object"}}
  }'

# OpenAI 兼容入口(非流式)
curl -X POST https://<your-commonsrv-host>/api/public/v1/chat/completions \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"deepseek-v4-pro","messages":[{"role":"user","content":"你好"}],"stream":false}'

# 异步任务:LLM 参数放在 input 顶层;input.params 仅作旧调用兼容
curl -X POST https://<your-commonsrv-host>/api/public/v1/ai/jobs \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model_key":"genlongtext",
    "input":{"messages":[{"role":"user","content":"写一篇深度分析"}],"max_tokens":8000,"thinking":{"type":"enabled"},"reasoning_effort":"high"}
  }'

curl https://<your-commonsrv-host>/api/public/v1/ai/jobs/$JOB_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN"

以上是完成 AI 接入的核心流程;需要逐字段请求/响应定义时,从统一入口进入 REST / OpenAI 兼容专题文档。

视频理解模型通过异步任务接收 input.messages,用户消息的content 数组包含 {type:"video_url",video_url:{url:"https://..."}}。 腾讯 TokenHub 的混元视频通道使用该协议,视频地址须可由上游访问,单个文件上限为 100 MB; 该模型的输出上限为 8192 tokens。模型是否可用取决于管理员完成通道密钥与计费配置。

longvideoparse 下的腾讯 MPS 多模态理解通道也使用上述异步任务入口和统一输入输出: 输入一个 video_url 与文本解析指令,成功时返回兼容 Chat Completion 的文本内容。 通道将请求提交为ProcessMedia 的 AiAnalysisTask(Definition 33),随后通过DescribeTaskDetail 查询结果;使用腾讯云 SecretId/SecretKey 与项目关联的 COS 存储通道。 该通道须由服务端验证视频时长后才可参与路由;供应商成本按视觉理解每分钟 1.5 元折算为每秒 0.025 元, 用户价格按当前渠道加成及积分汇率计算。使用前须配置 MPS 服务权限。

功能五:工具型接口(音色克隆 / 抖音解析 / 对象存储 / 数据抓取 / 短信…)

一次性工具型调用。按每次 / 每千字符 / 按上游实际用量计费,上游返回资产原样透传。 与功能四的区别:模型按 token / 时长走通用入口;接口是一次性工具,每个 key 参数不同,按 request_schema 动态发现。

动态发现(免鉴权)

const { items } = await CommonSrv.endpoints.list();
// items[i]: { key, display_name, category, async, request_schema }
//   - async=true 表示该 endpoint 内部走异步 job,call() 会自动等待终态返回。
//   - request_schema: 每个 endpoint 入参不同,按此 schema 传参 / 渲染表单。

统一调用

const r = await CommonSrv.endpoints.call(
  "<endpoint_key>",
  { /* 每个 key 的入参,见 request_schema 或下方示例 */ },
  { meta: { feature: "..." }, ref: { type: "your_app", id: "..." } }
);
// r: { ok, output, credits_charged, balance }

当前启用的 endpoints

以下清单以 CommonSrv.endpoints.list() 返回为准;新增 / 停用会实时反映,业务端无需改代码。

1) minimax-voice-design — 音色设计(语音)

MiniMax voice_design 音色克隆,返回 voice_id、私有媒体资产 ID 与短期试听地址。 CommonSrv 会将上游试听音频持久化到统一媒体存储;不会把原始音频 body 透传给客户端。 项目未开启媒体灰度或服务端存储配置不完整时,会在调用上游前返回ENDPOINT_MEDIA_STORAGE_UNAVAILABLE,不会产生该次上游调用费用。 其中 preview_text 为必填(上游用它合成试听音频), 本接口即按 preview_text 的字符数计费(中日韩字符按 2 计)。

const r = await CommonSrv.endpoints.call("minimax-voice-design", {
  prompt: "温柔女声,30 岁左右",
  gender: "female",
  age: "young",
  preview_text: "你好,这是一段试听文本。",   // 必填,计费依据
}, { meta: { feature: "音色克隆" } });
// r.output: { voice_id, media_asset_id, url, expires_at, trace_id, ... }
// url 是私有短期读取地址;业务表应保存 media_asset_id,不保存 url。
2) douyindl — 抖音解析(工具)

通过短链或 web 地址拿到抖音视频的无水印直链、封面、标题等。

const r = await CommonSrv.endpoints.call("douyindl", {
  url: "https://v.douyin.com/xxxxxxx/",
}, { meta: { feature: "抖音解析" } });
// r.output: 上游 body 原样透传(含 data / aweme_details 等抖音字段)
3) apify-run-actor — Apify Actor 抓取(工具)

通过 Apify Actor 抓任意站点。按 Apify run.usageTotalUsd 实际消耗换算积分。

const r = await CommonSrv.endpoints.call("apify-run-actor", {
  actor_id: "apify/web-scraper",
  run_input: { startUrls: [{ url: "https://example.com" }] /* 每个 Actor 不同 */ },
  // memory_mb: 2048, build: "latest",
  // fetch_items: true, item_limit: 1000, timeout_secs: 300,
}, { meta: { feature: "数据抓取" } });
// r.output: { items, run_id, dataset_id, usage_usd, ... }  // 上游 run 结构原样
4) brightdata — Bright Data(工具)

专门用于抓取 TikTok Shop 数据;入参按启用通道的 request_schema 提供。

const r = await CommonSrv.endpoints.call("brightdata", {
  /* 入参以 endpoints.list() 返回的 request_schema 为准 */
}, { meta: { feature: "TikTok Shop 抓取" } });
5) send-sms — 发送短信(工具)

统一短信发送入口。默认走「验证码 + 有效期分钟」两参模板(模板变量顺序固定为 {1}=验证码, {2}=有效分钟); 管理员在「接口管理 · send-sms」挂通道(腾讯云 / 阿里云 / 未来海外通道)并填 sign_name / template_id。output 已归一化契约,切换通道调用方零改动。

const r = await CommonSrv.endpoints.call("send-sms", {
  phone: "+8613800138000",       // 或 phones: [...] 批量(<= 200)
  code: "123456",                 // 验证码
  expires_in_minutes: 5,          // 有效期分钟(1–60,默认 5)
}, { meta: { feature: "发送短信" } });
// r.output(跨通道稳定契约):
// {
//   request_id,                  // 上游请求 ID
//   phone_count,                 // 本次调用手机号数(计费快照用)
//   accepted_count, failed_count,
//   per_phone: [{
//     phone,                     // E.164
//     status: "accepted" | "failed",
//     code,                      // 成功统一 "OK",失败为上游码
//     message,
//     provider_message_id?,      // 腾讯 SerialNo / 阿里子 BizId 等
//     fee?, country_code?,
//   }],
//   raw,                         // 上游原始响应(仅排障用,不算契约)
// }
6) storage-sign — 对象存储直传(OSS / TOS / COS 可配置)

原始 object_key 写入接口已停止开放。上传统一使用后文 uploadManaged,读取和清理统一使用资源 ID。

const asset = await CommonSrv.storage.uploadManaged(file, {kind: "video"});
// 保存 asset.id 到业务记录;关联通过 references/upsert 同步。
// 签名过期后按资源 ID 重新读取;删除先预览,确认后执行。

浏览器直传等无法由 CommonSrv 直接观测的失败,可由已认证应用通过POST /api/public/v1/client-events 上报诊断事件。仅允许阶段、错误和关联 ID 等结构化元数据,禁止上传文件内容、凭据或渠道配置。

功能六:邀请奖励(Invite / Referral · 一行接入)

加一个入口执行 CommonSrv.invite.open() 即可打开集成邀请界面。

document.querySelector("#menu-invite").onclick = () => CommonSrv.invite.open();

// 可选:CommonSrv.invite.open({ mode: "iframe", container: el });
// 可选:CommonSrv.invite.open({ mode: "redirect" });

存储容量与期限配置契约

配置了临时/长期双空间的项目无需旧超额清理协议:临时文件到各自保存日期后、长期文件到付费截止日加宽限天数后,由维护任务自动进入物理清理流程,不依赖用户登录。其他尚未启用双空间的项目仍沿用原有超额通知与业务清理协议。

配置双空间的项目为每人提供免费临时空间,并可单独用积分购买长期空间。订阅套餐只发积分,不提供空间容量。两种空间分别计量,文件不会自动转移;用户选择新资源的默认目标并保存偏好,新上传在创建资源时冻结目标,异步生成在受理任务时冻结目标。业务页面可用 CommonSrv.billing.open({ tab: "storage" }) 直接打开长期空间购买页。

配置双空间的项目调用 GET /api/public/v1/storage?view=space-usage;返回 temporary_quota_bytes、temporary_used_bytes、long_term_capacity_bytes、long_term_used_bytes,以及各自预占、剩余和选中目标、长期付费截止日期及保留截止日期。还返回图片/视频默认保存天数、长期到期保留天数、购买容量的最小值/步长/默认值/快捷列表/最大值,均来自项目配置。容量单位为字节,1 GB = 1024³ 字节。?view=usage 为其他项目的原有汇总接口。

图片/视频/音频任务在服务端原子预占容量,空间不足返回 409 STORAGE_QUOTA_EXCEEDED,不启动付费模型。异步受理后的拒绝任务在 GET /ai/jobs/:id 的 job.storage_requirement 和任务快照的 error_details 提供拒绝当时的 storage_space、required_bytes、available_bytes、shortfall_bytes;同步拒绝的 409 响应也带 storage_requirement。这些字段均为字节,客户端应显示需要、可用与差额,不应把当前空间页面读数当作当时快照。配置双空间的项目从「存储空间配置」读取视频每秒、图片每张的预留 MiB(1 MiB = 1048576 字节):当前初始值分别为 1 MiB/秒和 5 MiB/张,视频时长向上取整;请求未提供 seconds/duration、模型也无时长默认值时,按项目「存储空间配置」中的未知时长估算秒数(初始 60 秒)预留,不要求用户补填。明确传入非正数或非数字时返回 STORAGE_VIDEO_DURATION_INVALID。图片缺省一张,张数无效返回 STORAGE_IMAGE_COUNT_INVALID。模型 params.storage_output_budget_bytes 的显式预算仍优先;非双空间项目沿用视频 512 MiB、其他媒体 64 MiB。已接受任务实际结果超预算仍保存,随后限制新增。未知任务不因超时释放预占。

异步生成请求可选传 storage_space(temporary/long_term)及仅供临时空间使用的 storage_retention_days(正整数,不得超过项目配置上限)。省略目标时读取用户已保存的选择,首次为临时空间。受理时冻结目标和保存天数,并只在此时检查相应空间是否可用。长期空间过期返回 409 STORAGE_LONG_TERM_EXPIRED,需提示用户明确切换到临时空间;临时空间不足返回 409 STORAGE_QUOTA_EXCEEDED。已受理结果仍保存到原目标,即使完成时已超额或到期。

临时空间默认容量初始配置为 2 GB,图片默认保存 30 天、视频默认 7 天,业务最多指定 30 天;项目管理员可修改这些值,修改保存天数只影响新文件。长期空间初始价格配置为每 GB 每自然月 200 积分、到期宽限 30 天;购买容量初始最少 5 GB、步长 5 GB、默认 10 GB,快捷 50/100/1000 GB,均可在项目配置中修改。购买页只展示长期空间。先调用 ?view=credit-quote&action=buy|expand|renew&target_gb=整数 展示积分,再 POST /storage 提交 credit_purchase(含原报价 expected 和稳定 idempotency_key)。POST /storage 的 selected_space 保存用户新资源的默认空间,适用于新上传素材和省略 storage_space 的异步生成请求;异步生成请求显式传 storage_space 时以本次请求为准。普通上传创建资源时按当前默认空间检查容量和权益,成功创建后冻结目标;已受理的生成结果沿用任务受理时冻结的目标。auto_renew.enabled 保存自动续费。所有显示期限为项目日历时区的日期,到该日 23:59:59 结束;清理从次日开始。

普通上传在创建意图时若长期空间已到期返回 409 STORAGE_LONG_TERM_EXPIRED,若所选空间不足返回 409 STORAGE_QUOTA_EXCEEDED。用户可见的“免费临时空间”有自身容量与保存期限。平台内部使用的 temporary/<小时数>hour-cleanup/ 对象目录独立于用户空间,不允许将正式用户素材上传到该目录;应用始终保存并传递资源 ID。

资源状态为 pending、ready、failed、deleting、deleted。GET /storage 可按 state、after、limit 查询。CommonSrv.storage.open() 提供通用容量与关系页面;应用的主整理入口应按作品和版本实现。CommonSrv.assets.changeMany(items) 仅支持 organize 元数据修改;save、trash、restore 和 saveMany 已退役。

永久删除先 POST /storage 传 deletion={action:"preview",asset_ids:[UUID]},返回不可扩大的计划、版本快照、阻止原因和 estimated_released_bytes;确认传 deletion={action:"confirm",plan_id:UUID}。确认幂等,返回每项 accepted/skipped 与 released_bytes。新增引用、活跃任务、未知关系和版本变化均会阻止执行。逻辑容量释放不代表对象存储已完成物理回收;没有撤销和回收站。

配置双空间的项目不再提供现金购买存储订单;使用积分购买长期空间。新购、扩容和续费都通过 /storage 的积分报价与确认接口,积分不足不透支。尚未配置双空间的其他项目保留原有 storage_service 接口。

双空间项目没有容量缩减时按作品挑选超额文件的流程。长期空间到期后停止新的长期生成;文件保留到配置的统一宽限截止日。宽限期内续费可继续保留原文件;过期后自动清理。临时文件按各自固定日期清理。运行中任务及未确认的资源关系仍受现有安全锁保护,保护解除后维护任务继续清理。

读取意图与实际读取分开记录。签发地址不能作为下载证明;HEAD/Range 可用性探测不计为用户访问。访问覆盖未知时不得推断为冷数据。本期不启用自动冷存储转换。

浏览器 SDK 3.17 的 CommonSrv.storage.uploadManaged(file, options) 统一处理上传。options 支持 sourceRef、visibility、idempotencyKey、waitMs(0–180000)、signal、onPrepared、onProgress。成功返回 id、status、url、url_expires_at,不返回存储位置或凭据。status=processing 时额外返回 completion_file_id,仅用于恢复确认:保留 id 并调用 completeManagedUpload(id, completion_file_id),不要重传。请求失败时错误对象可带 assetId/completionFileId。SDK 不因网络错误自动删除资源。

客服工单截图可在上传意图中指定 purpose: "support-feedback";仅接受图片,单张最多 10 MiB,供同一用户的客服工单引用,720 小时后由平台清理,不计入用户素材空间。普通素材不要设置该用途。组合任务的中间文件期限由平台组合通道配置,公开上传接口不接受业务自行指定临时对象路径或过期小时数。

统一上传会自动为本次操作发送 X-Upload-Trace-Id(UUID),创建、凭证、分片和完成确认共用链路;并发文件分别分组,失败对象带 traceId 便于定位。此标识只用于日志,不作为授权凭据。完成确认返回 HTTP 200 不代表资源已就绪,仍须检查 status。日志详情与复制只包含选中的单条记录。

SDK 3.17.2 自动向 client-events 上报浏览器传输成功或失败,标记“客户端上报”并使用同一 correlation_id。仅含资源 ID、文件大小、耗时与结果,不含上传凭据、路径或文件内容。上报失败不会改变上传结果。完成确认日志根据 asset.status 区分处理中、已就绪、失败,HTTP 状态码仍按原响应显示。

所有 storage-sign 通道的传输差异由 SDK 内部处理,包括 CloudBase;应用无需安装存储厂商 SDK,也无需提供 cloudbaseUpload。新素材上传统一为服务端签名 PUT,浏览器无需加载 CloudBase 组件或登录 CloudBase;旧 cloudbaseUpload 仅作过渡兼容。分步 REST 接入的 env_id、ticket、object_key 属于直传协议参数,不应保存到业务字段;ticket 和签名在日志写入、展示与复制时脱敏。原始 storage.upload 不再作为公开写入入口,不能将 object_key 当成资源 ID。

统一资源上传使用 intents → upload-ticket → 直传 → complete。新签发 upload-ticket 的 upload.transport 统一为 put,使用 method=PUT、url、headers 原样直传,确认时 file_id 传 completion_file_id;旧 PUT 响应缺少此字段时可使用 object_key。旧 cloudbase 响应(省略 transport 也按 CloudBase)由 SDK 兼容,使用 env_id/ticket。file_id 是历史字段名,客户端不得自行拼接。两条路径都返回同一资源 ID,并核验大小、文件签名和封存状态;当前统一直传上限 512 MB,原有大文件分片接口仍保留,尚不能当作受管资源上传替代。存储商由服务端选择,业务只持有资源 ID,不接触密钥。

同一上传 idempotency_key 只能重试相同的类型、大小、业务来源和可见性,冲突返回 409 MEDIA_IDEMPOTENCY_CONFLICT。明确指定的上传通道禁用、重复或不可用时返回 503 MEDIA_UPLOAD_BACKEND_UNAVAILABLE,不自动换存储商。取消先锁定待上传记录再删除对象,已完成或被取消锁定的上传不能再次确认(409 MEDIA_UPLOAD_NOT_PENDING);删除失败保留容量预占和原对象位置,可重试原 abort。确认请求超时应重试原 complete,不要因响应丢失主动删除文件。

GET /api/public/v1/storage?view=thumbnails&ids=UUID,UUID 使用用户 Bearer 身份,最多 100 个资源 ID,返回 thumbnails(id、url、expires_at);未就绪/已删除/无权访问时 url 为 null,不返回原文件作为替代。小图与原件共享归属检查,签名最长 300 秒。文件保留期限与访问凭证有效期相互独立。COS/OSS/TOS 的受管读取支持不足 60 秒的短有效期,不向上放大。

封存对象使用服务端专属 sealed-assets 命名空间。公开 storage-sign 接口不能直接读写/删除该命名空间,返回 STORAGE_RESOURCE_ID_REQUIRED;必须使用资源 ID 接口进行归属检查。封存、缩略图处理和永久保留策略核验分别记录,复制成功不代表已经通过永久保留核验。

资源就绪后自动保存。双空间项目的临时资源使用受理时确定的保存天数计算截止日期;长期资源使用账户统一的付费日期与宽限天数。

双空间项目的临时资源有固定保存截止日期;不要把读取签名的 expires_at 当成文件清理日期。

CommonSrv 统一支持已配置的 CloudBase、腾讯 COS、火山 TOS、阿里云 OSS。业务端不用选择厂商或处理桶路径。CloudBase 换链按环境分组、每批最多 50 个;原生返回时长不足时,仅对 SDK 确认的真实 COS 对象尝试重签并验证读取权限,验证失败保留原短链并阻止超出期限的生成;COS/TOS/OSS 在服务端本地签名。它们都不提供一次请求返回任意 100 张图片内容尺寸的通用接口,缺少内容证据时仍需有界并发读取。其他对象存储尚需独立适配,不能仅因兼容 S3 就视为已经接入。

整批生成先调用 CommonSrv.storage.beginBatch(key, assetIds, jobKeys),对应 POST /storage 请求 batch 对象包含 operation=begin、key(最多 200 字符)、asset_ids(最多 2000 个 UUID)、job_keys(1–1000 个、每项最多 200 字符)。响应 batch 包含 id、state、dispatch_closed、protected_until、automatic_until。相同 key 必须保持相同输入清单和任务键。使用 CommonSrv.storage.resolveManaged(ids, {batchId}) 获取资源,SDK 自动按 100 个分块;提交 /ai/jobs 时传 meta.input_batch_id、meta.input_media_ids,idempotency_key 必须来自 jobKeys。浏览器 SDK 和 SDK Core 的 ai.submit 第三个参数可传 idempotencyKey、mediaIds、batchId。

全部子任务已登记后调用 CommonSrv.storage.closeBatch(batchId),即 POST /storage 的 batch={operation:"close",batch_id}。关闭阻止迟到的新派发,已登记任务继续使用自己的保护;网络结果不确定时先查询原任务,不能另起付费任务。普通单次 /ai/jobs 带受管输入时服务端自动登记单任务批次。批次默认滚动保护 6 小时、剩余不足 1 小时续租、自动上限 24 小时;超限停止新增派发并核对未完成任务,未知状态继续阻止物理清理。批次保护阻止删除在用资源;资源读取不因账户满额而停止。

资源存在性不运行全库定期扫描,也不接收云存储对象事件。POST /media/assets/resolve 只核对本次页面实际请求的 1–100 个资源;生成任务派发只核对本次任务实际引用的资源。未访问的历史资源不会产生存储请求。404 记录为缺失;403 或网络异常记录为暂时无法确认,不误判删除。

存储策略管理页可调整签名目标、并发、取图余量和保护/巡检时间,修改带版本冲突检查及审计。资源页显示实际占用批次和文件可用性。STORAGE_FILE_MISSING 表示文件缺失;STORAGE_CONTENT_VERSION_CHANGED 表示内容或封存条件变化;STORAGE_FILE_UNVERIFIED 表示暂时不能确认可读;STORAGE_BATCH_REVIEW_REQUIRED 表示需要核对长时间未结束的任务。批量解析、缓存命中和运行保护均按应用及用户隔离。

上传与模型生成统一由服务端 resolveStorageBackend 选择目的地。唯一依据是 CommonSrv 后台项目「存储管理」明确选择的写入通道;每个项目必须选择,不提供系统默认或继承。「接口管理 → storage-sign」只管理通道连接和凭据,CloudBase 也登记为该服务的存储通道。环境变量只提供凭据,不参与目的地判断;通道优先级、权重、旧 allowlist 均不能覆盖后台选择。未选择返回 STORAGE_CHANNEL_NOT_SELECTED,通道停用或停止写入返回 MEDIA_UPLOAD_BACKEND_UNAVAILABLE;不会自动换到其他通道。历史资源和进行中的上传始终按各自记录的位置读取、重签、恢复和删除,不随项目选择改变。原对象核验使用源文件元数据;CloudBase 使用 COS GET Range 0–0 的总长度,其他存储使用 HEAD,不使用展示图的下载大小。

模型通道后台的 Mock 图片/视频/音频上传使用后台登录所属的平台项目 OpenTiger(__platform__)的写入通道,请在「项目管理 → OpenTiger → 存储管理」选择并保存;Mock 用户白名单不决定上传目的地。平台项目的既有 ID 00000000-0000-0000-0000-000000000001 可用于项目配置、存储管理与存储 Hook 的 project_id;权限和 Hook 项目授权仍需单独满足。配置只保存可跨环境解析的存储引用,实际模拟结果与管理页预览会按当前环境重新签发短期读取地址。本地 Mock 开关属于环境运行状态,发布配置同步不会覆盖 Production 的开关;Production 配置检查会拒绝 localhost、127.0.0.1 与 ::1 的历史 Mock 媒体 URL。

购买目录 plans 返回会员权益档位及 offers[] 报价;双空间项目的档位只含积分,storage_quota_bytes 为 0。每个报价包含 id、currency、mode、price_cents、enabled,连续订阅另含 first_price_cents;套餐购买仍通过 /orders 或 /subscriptions。

连续订阅可 GET /api/public/v1/subscriptions 查询签约状态,DELETE 关闭自动续费。套餐订阅与长期空间积分续费是两套独立日期;套餐只发积分,不延长存储。

双空间项目的长期空间采用项目后台配置的积分单价,首次购买选择目标容量,扩容选择本次增加的 GB(接口仍提交扩容后的目标总 GB);按自然月购买,扩容只补当期剩余自然日差额。手动购买、手动续费与定时自动续费的积分流水分别标明来源;公开购买接口的 idempotency_key 不可使用保留的 auto: 前缀。自动续费于当前有效期截止日尝试按届时单价扣除下一期整月积分;积分不足时不扣款、关闭自动续费,并在积分明细留下 0 积分的失败记录。买入或自动续费成功后统一延长账户日期,不逐文件更新。

多币种报价分别配置,订单使用所选报价的币种。真实微信 Native 下单目前只支持 CNY;非 CNY 报价不能通过该渠道购买,接口返回 PAYMENT_CURRENCY_UNSUPPORTED。项目 Mock 渠道可测试其他币种报价;实际多币种收款需接入支持对应币种的支付提供方。

双空间项目的 GET /api/public/v1/plans 中 storage_quota_bytes 为 0;订阅权益只有积分。免费临时空间与长期空间容量从 GET /api/public/v1/storage?view=space-usage 获取。

双空间项目在后台「存储空间配置」修改免费容量、生成视频每秒和图片每张的预留 MiB、未知视频时长的估算秒数(初始 60 秒)、默认及最长保存天数、长期宽限天数、积分单价、最大购买 GB 和日历时区;输入先进入草稿,统一点击顶部“保存修改”才生效。

下载签名采用分层策略:生成输入及受管资源解析使用项目「生成资源 · 校验与有效期」配置;生成输入申请时长为 max(项目下载申请时长, 取图窗口 + 项目取图余量),取图窗口未设置时使用模型最大执行时长或通道执行超时。普通输出展示、mock 预览和元数据核验沿用 storage-sign 通道的读取默认值。实际链接有效期还受通道最大有效期、临时凭据和文件保留期限限制,以返回的 expires_at 为准;不足生成取图窗口时拒绝派发。本页参数不控制上传票据有效期。CloudBase 读取使用底层 COS 签名链接,元数据核验采用 GET Range 单字节请求;失败不回退原生短链接。配置修改只影响后续申请,缓存中仍满足所需时长的链接可以复用,已签发链接不会因此续期。项目配置版本 0 表示使用系统默认值;保存不覆盖已建立批次的策略快照。资源存在性按请求核验;旧版全库扫描及其状态表已退役,配置页不再查询或展示旧扫描完成记录,设置核对间隔不会启用周期扫描。

图片通道配置的 upstream_timeout_ms 按已保存的正整数毫秒值生效,不再被内部 120 秒下限覆盖;缺失时沿用原默认 180 秒,非法值返回 CHANNEL_MISCONFIGURED。

上传票据与下载链接分开管理:COS/OSS/TOS 上传使用通道 default_upload_ttl,不再固定覆盖为 600 秒。CloudBase 上传期限从供应商签名解析,不假定为 120 秒或 900 秒;无法解析返回 CLOUDBASE_STORAGE_UPLOAD_EXPIRY_UNKNOWN,已过期返回 CLOUDBASE_STORAGE_UPLOAD_EXPIRED。文件传输 claim 的 destination.expires_at 是签名截止时间,浏览器和 Worker 将单次上传超时限制为 min(配置的 I/O 超时, 票据剩余时间),剩余时间小于或等于 0 时停止该次上传,沿用原有放弃/重试流程,不重新生成结果或重复扣费。浏览器上传失败日志可带 provider_error_code,HTTP 400 本身不代表票据过期;不记录原始响应正文或签名。

公开 raw-key storage-sign 请求统一返回 STORAGE_RESOURCE_ID_REQUIRED。所有厂商均使用统一资源 ID 接口,访问始终按资源记录的位置进行。

CommonSrv.storage.open() 打开创作空间页。双空间项目使用 ?view=space-usage 和 POST selected_space;其他项目的 storage.getUsage() 仍使用原汇总接口。assets.previewDelete(ids) 与 assets.confirmDelete(planId) 用于用户主动永久删除。

GET /api/public/v1/storage 仅返回当前应用内本人的文件,资源记录包含 storage_space 和临时空间的 retention_deadline;支持 state、after、limit、kind、min_bytes、category 查询。双空间使用 ?view=space-usage,其他项目仍可使用 ?view=usage。

POST /api/public/v1/storage 的 items 只支持 organize:含 id、version、action 和可选 title、category、starred,最多 100 项,逐项返回 ok/error。收藏和分类不影响容量与保留期限。删除使用独立 deletion 预览/确认协议;直接调用媒体删除入口返回 STORAGE_MANAGED_DELETE_REQUIRED。

受理时所选空间不足返回 HTTP 409 STORAGE_QUOTA_EXCEEDED;长期空间过期返回 STORAGE_LONG_TERM_EXPIRED。已受理任务的结果仍保存,不在保存时重查容量。STORAGE_IN_USE、STORAGE_REFERENCE_PENDING、STORAGE_RUNTIME_IN_USE 和 STORAGE_VERSION_CONFLICT 是用户主动删除时的保护结果。

可信业务服务通过 POST /api/public/v1/storage/references(仅 X-Api-Key,不是浏览器用户令牌)提交 events,最多 100 项:asset_id、entity_type、entity_id、slot、单调递增 revision、state(active/released)、title、breadcrumb,以及可选 intent_id。仅允许关联本产品资源,资源承担账户由服务端查询确定;旧 revision 返回 ok: true、superseded: true、current_revision,不会确认该旧事件的 intent,业务端不得将其标为已投递,须先核对远端当前引用;同 revision 内容冲突拒绝。跨环境恢复后,应在投递前核对资源与引用、调整后续 revision 基线,并仅重投当前业务状态,不能盲目重放旧 Outbox。title 应使用业务名称,breadcrumb 应包含真实工作空间和业务用途;分镜可包含序号和版本。展示信息在业务事务生成事件时固定,重试不能改写;更名或补齐历史用途须追加新 revision。缺少关联只表示待核验,不表示资源闲置或可删除。

新绑定前,业务服务先校验业务写权限与资源访问权限,再向同一路径提交 operation=prepare、intent(id 幂等 UUID、asset_id、entity_type、entity_id、slot),获得 pending 意图;旧引用此时仍保留。业务写入和带 intent_id 的 outbox 同事务提交,随后发送 active 事件,匹配已提交投影才确认意图。提交结果不明时继续保护,不按时间自动释放。待确认意图会令清理返回 STORAGE_REFERENCE_PENDING。按路径删除不能绕过资源保护。

失败绑定可由项目后端提交 operation=cancel 和原 intent;必须先在业务数据库中不可逆地终止对应写入(以同事务行锁状态机拒绝迟到写入),不能只凭超时取消。取消使用原 ID 幂等重试;cancelled 墓碑拒绝迟到 prepare,已 confirmed 不能取消。Drama 集成以本地事务栅栏和取消 outbox 实现此协议;迁移前没有栅栏的旧意图不自动取消。

空间列表中的 pending_references 为当前用户资源的待确认关联(业务类型、记录 ID、槽位、创建时间);不应将暂时没有已提交 references 的资源直接显示成可删除闲置资源。

业务删除与资源删除是两件事:删除受管业务记录只提交 released 引用,不能直接按旧对象路径删除共享媒体。已有资源 ID 时下载必须重新解析该 ID,拒绝访问/已到期不能回退旧 URL 或预处理路径。公开原始对象路径接口统一返回 STORAGE_RESOURCE_ID_REQUIRED。

Drama 分段输出、生产来源 metadata 和成品 manifest 的显式 media_asset_id 使用 JSON 路径作为引用槽位(如 output.data.0.media_asset_id),与业务更新同事务入队;不从 URL 猜测资源归属。新 ID 或新绑定意图必须先 prepare,缓存 URL 刷新不改变引用,移除 ID 会释放对应引用。生产视图每次读取重新解析受管 ID;到期、越权或解析失败清空缓存链接,不回退旧地址。仅存 URL 的历史来源仍属兼容数据,并不代表已纳管。

异步 AI Job 的 meta.input_media_ids 可携带最多 100 个输入资源 UUID;服务端在扣费/调用模型前按当前产品和用户验证、原子锁定资源。任务输入清单不可在创建后修改;空间列表 runtime_tasks 展示仍在使用的任务。资源不存在/越权返回 403 STORAGE_JOB_INPUT_UNAVAILABLE;没有暂存转存步骤;已保存资源在未删除且可核验时可作为任务输入。成功任务或确定未出站的扣费预授权失败自动解除保护;一般失败、取消和未知结果不按时间自动解除,需核对上游结果。URL 本身不推断为资源 ID,尚未携带 ID 的旧入口不计入已覆盖。

工具接口 POST /endpoints/:key 也支持 meta.input_media_ids(最多 100 个 UUID),携带时必须提供 idempotency_key。输入按产品和用户锁定,相同键不能改变业务参数或资源清单;同对象签名续期不视为参数变化。执行中/结果未知返回 202 in_progress,不能更换幂等键盲目重试。确定未出站的拒绝释放保护并允许原键重试;已成功的同步结果和工具异步任务查询成功后释放保护。保护校验冲突返回 409;没有携带 ID 的历史 URL 不算已覆盖。

浏览器 SDK 3.15 的 uploadManaged 默认对 16 MB 以上文件尝试受管分片,固定 8 MB 分片、总文件上限 512 MB。COS/OSS/TOS 共享服务端会话;CloudBase 明确不支持此协议时才保留原 SDK 传输,不因权限或网络错误切换后端。onPrepared 返回 assetId,可持久保存该 ID;中断后重新选择原文件并传 assetId 恢复。SDK 分块计算 SHA-256 指纹,文件内容变化拒绝续传,不缓存签名或令牌。

受管分片使用 POST /media/assets/:id/upload-ticket,action 为 multipart-begin(fingerprint 必填)、multipart-status、multipart-part(part_number 必填)、multipart-complete、multipart-abort。服务端固定通道、对象、大小及分片边界,客户端不能指定 uploadId。上传授权十分钟、会话默认二十四小时;服务端从存储商查询已完成分片,不依赖浏览器自报 ETag。complete 合并核验后仍需用 completion_file_id 调用资源 complete,等待封存就绪。取消先终止上传 ID,再回收意图;合并中不能取消。创建回执丢失按唯一对象查询上传会话,查不到或多个候选保留预占并返回 MEDIA_MULTIPART_RECONCILIATION_REQUIRED,不重复创建上传。过期会话不能继续签发分片,但已完成上传仍可确认。

分片授权绑定精确 Content-Length,浏览器必须发送原文件对应的 Blob.slice,不手动设置浏览器禁用的 Content-Length 头。重复取消已删除意图返回成功,但不声称再次删除了文件。取消后保留原先分配路径的迟到上传监测;缺少 CloudBase fileID 时只核验服务端已分配路径,不猜测其他对象。未知结果继续保留观察,自动物理删除需另行启用。

资源列表 runtime_operations 展示仍受工具调用保护的资源与 operation_key,区别于异步 AI 的 runtime_tasks。后台单列已取消上传的 observed_present_bytes、未知/待检查数量和采集时间,不把它们冒充完整桶账单。有资源或迟到上传监测记录的通道不能原地更改桶/区域/端点,返回 STORAGE_CHANNEL_NAMESPACE_IN_USE;迁移位置应使用新通道并验证迁移,凭据轮换不受此限制。

后台容量统计包括上传与 AI 预占;7/30/90 天图按真实观察时间显示。源站 HEAD 观察仅覆盖已登记的位置(原件、封存副本、缩略图),单独标注缺失/未知数量,不等同于全桶清单、历史版本或流量账单。

功能七:个人中心 & 语言偏好(一行接入)

加一个入口执行 CommonSrv.profile.open() 即可打开集成个人中心(昵称、绑定信息、语言偏好、退出登录)。

document.querySelector("#menu-profile").onclick = () => CommonSrv.profile.open();

// 可选:CommonSrv.profile.open({ mode: "iframe", container: el });
// 可选:CommonSrv.profile.open({ mode: "redirect" });

读取当前语言

const lang = await CommonSrv.lang.getLang();   // "en" | "zh-CN" | "zh-TW"

用户改语言只走 profile.open() 页面(登录入库,未登录存本地),业务端只读不写。

⚠️ 必接:语言切换 / 退出登录 的宿主同步

这是接入方必须处理的一步,否则会出现以下 Bug:
  • 用户在 profile.open() 里切换了语言,但接入方站点自身的 i18n 没跟着变。
  • 用户在 profile.open() 里点了「退出登录」,但接入方站点仍显示为已登录(因为退出的只是 CommonSrv 的会话,不是宿主自己的用户态)。

方案 A · 零代码(默认,推荐给多页面 / 传统站点)

SDK 默认在收到「语言变更」/「退出登录」消息时整页 location.reload()。多页面站点、后端渲染(SSR)站点、传统 jQuery / 服务端模板站点直接用默认值即可,什么都不用写。

// 默认行为 —— 什么都不用配
CommonSrv.configure({ clientId: "<your_client_id>" });

方案 B · 自己接管(推荐给单页应用 SPA:React / Vue / Angular / Svelte 等)

什么是 SPA(Single Page Application,单页应用):整站只加载一次 HTML,之后靠前端路由切页面 —— React / Vue / Angular / Svelte / Next.js(客户端路由部分)都算。 SPA 页面一旦被 location.reload(),路由、表单、弹窗、Redux/Pinia 等前端状态会全部丢失,体验很差。 请关掉自动刷新并订阅两个事件,自己切 i18n / 清用户态。

CommonSrv.configure({
  clientId: "<your_client_id>",
  autoReloadOnLangChange: false,   // 关掉「语言变更整页刷新」
  autoReloadOnLogout:     false,   // 关掉「退出登录整页刷新」
});

// ① 语言变更 —— 用户在 profile.open() 里切了语言就会派发
CommonSrv.on("lang:change", (lang) => {
  // lang: "en" | "zh-CN" | "zh-TW" | ...
  i18n.changeLanguage(lang);   // 换成你自己的 i18n 库调用
  // 或者:window.__setAppLang(lang); / router.invalidate() 等
});

// ② 退出登录 —— 用户在 profile.open() 里点了退出,或跨标签页同步的退出
CommonSrv.on("auth:change", (e) => {
  if (e.type === "signout") {
    // 清接入方自己的用户态、跳登录页、清缓存 …
    yourStore.clearUser();
    router.navigate("/");
  }
});
要点:
  • lang:change / auth:change 是 SDK 自动派发的事件,你只负责订阅,不需要自己 postMessage 或轮询。
  • 关掉 autoReloadOnLogout 后,SDK 仍会在内部清掉 CommonSrv 的会话;你只负责清宿主自己的用户态。
  • 方案 A 和 方案 B 二选一:默认就是方案 A;改了 configure 才走方案 B。
  • 老版本 SDK(< 3.10)没有这两个配置项,请把 <script src="…/sdk/commonsrv-browser.js"> 上的缓存刷掉,或加 ?v=3.10。

错误码速查

  • 402 INSUFFICIENT_CREDITS:积分不足,响应体附带 required 与 current,引导 CommonSrv.billing.open()。
  • 404 MODEL_NOT_FOUND:传入的 model_key 不存在或已停用。
  • 403 MODEL_DISABLED:模型已被禁用。
  • 400 USE_JOBS_ENDPOINT:该模型是异步类别,请改用 CommonSrv.ai.submit / .wait。
  • 429:限流(默认每用户 60 req/min),稍后重试。
  • 502 AI_INVOKE_FAILED / MODEL_NOT_ROUTABLE:调用失败,可重试。
  • 400 AMOUNT_BELOW_MIN(邀请奖励 · 提现):提现金额低于后台配置的 min_payout_cents(默认 ¥100)。
  • 400 INSUFFICIENT_BALANCE(邀请奖励 · 提现):现金钱包余额不足。
  • 409 PENDING_WITHDRAWAL_EXISTS(邀请奖励 · 提现):24 小时内已有待审核申请,请稍后。

错误响应与任务错误展示

公共错误响应的 error 是稳定错误码;任意异常文本会收敛为REQUEST_FAILED 或 SERVICE_UNAVAILABLE。原始 服务商专属错误码会收敛为 UPSTREAM_ERROR,detail 保留在受限调用日志,不随共享错误响应返回。 异步任务查询的 job.error 仅返回适合用户阅读的原因,原始失败记录保留在任务及调用日志。 客户端按错误码选择操作,向用户展示本地化说明,不直接展示内部错误码。

字段用途是否给用户看
err.code机器可读错误码(英文常量),业务侧 switch 判断分支用❌ 别直接 toast — 请映射成你自己的多语言文案
err.data.detail共享错误响应不再提供原始明细;到受限日志排查❌ 不应依赖或展示
err.statusHTTP 状态(402 = 需充值,429 = 限流…)✅ 可以按 status 分类提示
try {
  await CommonSrv.ai("google/gemini-2.5-flash", { messages: [...] });
} catch (e) {
  const MESSAGES = {
    INSUFFICIENT_CREDITS: { "zh-CN": "积分不足,请前往充值", en: "Insufficient credits" },
    MODEL_NOT_FOUND:      { "zh-CN": "模型不存在",           en: "Model not found" },
  };
  toast(MESSAGES[e.code]?.[locale] ?? "请稍后重试 / Please retry");
  console.error("[commonsrv]", e.code, e.data);
}