Files
Genarrative/docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md
suzmii 736a1b6ac6
Project CI / Repository checks (pull_request) Successful in 2m36s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / Backend tests (pull_request) Successful in 7m35s
Project CI / Native shell tests (pull_request) Failing after 7m43s
接入 LLM Router 累计额度结算
按 Router used_quota 累计值与首次基线结算泥点
新增原子 checkpoint 事务及 llm_router_consume 钱包流水
同步额度查询校验、前端展示、生成绑定和技术文档
2026-09-06 00:36:31 +08:00

37 KiB
Raw Permalink Blame History

外部 OpenAPI 与 API Key 接入方案

背景

外部调用方需要通过稳定 HTTP 契约或托管式远程 MCP 使用图片画布编辑器内的素材生成、编辑和管理能力,并能创建项目、保存画板布局和管理账号级素材库。不支持 MCP 的 Agent 还需要可发现、可校验、可完整下载的 Skill 包,而不是只有一份 OpenAPI JSON。全部入口必须走 server-rs + Axum + SpacetimeDB 正式链路,不能把 API Key、画板状态、素材状态、生成任务或生成结果放到前端临时状态中。

v1 范围

本期新增外部 API 命名空间:

/api/external/v1

v1 只开放以下能力:

  • POST /api/external/v1/assets/direct-upload-tickets:创建素材直传 OSS 凭证。
  • POST /api/external/v1/assets/objects/confirm:确认已上传素材对象,ownerUserId 固定为 API Key 所属账号。
  • GET /api/external/v1/assets/read-url:获取私有素材读取签名 URL。
  • GET /api/external/v1/editor/projects:列出当前 API Key 所属账号的图片画布项目;view=full|summaryREST 默认 fullMCP 固定使用 summary
  • POST /api/external/v1/editor/projects:创建图片画布项目。
  • GET /api/external/v1/editor/projects/recent:读取当前账号最近图片画布项目。
  • GET /api/external/v1/editor/projects/{projectId}:读取项目与默认画布。
  • PATCH /api/external/v1/editor/projects/{projectId}/metadata:更新图片画布项目标题。
  • DELETE /api/external/v1/editor/projects/{projectId}:删除图片画布项目,并级联清理默认画布和项目资源元数据。
  • PATCH /api/external/v1/editor/projects/{projectId}/canvas:保存默认画布的 viewport 和 layers。
  • POST /api/external/v1/editor/projects/{projectId}/resources:创建项目画布资源记录。
  • GET /api/external/v1/editor/assets/library:读取账号级编辑器素材库。
  • POST /api/external/v1/editor/assets/folders:创建素材文件夹。
  • PATCH /api/external/v1/editor/assets/folders/{folderId}:更新素材文件夹名称或折叠状态。
  • DELETE /api/external/v1/editor/assets/folders/{folderId}:删除素材文件夹并返回最新素材库。
  • POST /api/external/v1/editor/assets:创建素材记录。
  • PATCH /api/external/v1/editor/assets/{assetId}:更新素材名称或所在文件夹。
  • DELETE /api/external/v1/editor/assets/{assetId}:删除素材记录。
  • POST /api/external/v1/editor/images/generations:异步提交编辑器图片素材生成;通过 kind 支持普通图、规范图 spec、角色图 character、快速编辑参考图 quick-edit、UI 设计图 ui-design 和宣发素材 publication-material。External v1 当前不开放结构化游戏场景生成,kind = sceneassetKind = scene 均在入队前返回 400
  • POST /api/external/v1/editor/images/edits:异步提交已有图片重绘 / 调整。
  • POST /api/external/v1/editor/images/background-removals:异步提交已有静态图片去背景;只接受当前账号拥有的稳定 objectKey、项目资源 ID 或素材 ID。可选 assetKind 必须与权威来源类型一致,视频、音频、动画和图片序列类型在入队前返回 400;拒绝 taskId 与其它未声明字段。
  • POST /api/external/v1/editor/icon-spritesheets/generations:异步提交规范图驱动的图标 spritesheet 生成和拆分。
  • POST /api/external/v1/editor/ui-designs/assets/extractions:异步提交 UI 设计图素材提取 / 拆分。
  • POST /api/external/v1/editor/character-animations/generations:异步提交角色动画预览和帧序列生成。
  • POST /api/external/v1/editor/videos/generations:异步提交编辑器视频生成,支持现有 Seedance / Kling / Veo 模型参数和参考媒体限制。
  • POST /api/external/v1/editor/audios/sound-effects/generations:异步提交编辑器音效生成。
  • POST /api/external/v1/editor/audios/background-music/generations:异步提交编辑器背景音乐生成。
  • GET /api/external/v1/generations/{operationId}:按 API Key owner 查询异步生成状态;completed 时返回 compact 稳定结果引用,跨 owner 按不存在处理。
  • GET /api/external/v1/openapi.json:导出本版本 OpenAPI 3.1 JSON。
  • GET /api/external/v1/agent-integration.json:公开导出 Agent 集成发现 manifest,声明 MCP、OpenAPI、Skill 入口、完整 Skill archive、archive SHA-256 和包内文件清单。
  • GET /api/external/v1/skill/SKILL.md:公开读取 Skill 原始入口。
  • GET /api/external/v1/skill.zip:公开下载完整 Skill 包。
  • POST /api/external/v1/mcp:使用相同 Bearer API Key 的托管式 Streamable HTTP MCP;对外暴露本节 OpenAPI operation tools 以及使用说明、OpenAPI、Skill 入口 SKILL.md 和逐个 Skill reference 文档,不开放内部 SpacetimeDB MCP。

九类生成 POST 全部要求 Idempotency-Key,成功只返回 HTTP 202 AcceptedoperationIdkindstatusstatusUrlpollAfterMsupdatedAtMicros。调用方不得把 202 当作媒体生成完成,也不得在网络结果不确定时换一个幂等键重新提交。

图片生成、图标 spritesheet 和 UI 素材提取的 completed compact result 可携带可选结构化 warning { code, reason };任务查询顶层 warning 是可直接展示的有界摘要。外部 OpenAPI 当前公开四个稳定 code

  • postprocess-failed-source-preserved:生成成功,但透明处理、像素规整等后处理未完成,接口保留仍可使用的原图或进入该步骤前的结果。
  • dimension-restore-fallback:该告警只描述进入可选 pixelArt 处理前的交付尺寸归一结果;无法安全归一时,在该处理边界保留 provider 回图尺寸。它不描述或约束 pixelArt 成功后的最终尺寸;若随后像素规整成功,最终产物是整数倍放大后的 PNG,不能据此推断最终宽高等于 provider 回图。
  • unsupported-image-style:请求的图片后处理风格未知或不适用于当前生成类型,接口按无风格继续生成。
  • multiple-generation-warnings:同一成功响应合并了不同 code 的多条非阻断告警,具体原因按顺序拼接在 reason

手工调用 POST /api/external/v1/editor/assetsPOST /api/external/v1/editor/projects/{projectId}/resources 创建 assetKind=character-animation 记录时,generationInputs 只保存可重放的生成输入,不接受 characterAnimationframespreviewVideoPathframeCountfpsdurationSeconds 等旧运行字段;完整正式帧序列和总时长必须分别写入 imageSequenceFramesimageSequenceDurationMsscreenColorHexmattingProvidermattingModel 属于内部处理审计字段,External v1 会在持久化前移除。角色动作生成接口已经直接返回正式 resource / asset,正常调用方不应再手工复制第一帧创建重复记录。

provider 原图已保存但透明背景处理最终失败时,worker 保留原图稳定引用,不返回不存在的透明处理图,图标和 UI 也不继续拆分;有 projectId + canvasCompletion 时由原图完成画布写回。调用方应展示告警,但不得把 completed 任务改判为失败。该降级只覆盖透明背景处理的最终失败,phase 上报、原图或透明处理图持久化、画布写回失败仍使任务失败。图标 / UI 已成功生成透明图、只有自动拆分失败时继续使用既有 sliceWarning。通用 warningsliceWarning 只在「透明背景最终失败」这一条上互斥;风格归一化或像素规整产生的通用 warning 可以与 sliceWarning 并存,compact result 不得丢弃任一条。

异步提交、查询与幂等

外部生成不受 GENARRATIVE_EXTERNAL_GENERATION_MODE=inline 影响:无论站内本地排障模式如何配置,External v1 都只持久化入队并返回 202,不在 API 请求中同步执行 provider。正式状态源是既有 external_generation_job;生成核心、计费、OSS、画布写回、lease 续租和 fencing 继续由现役 worker 链路负责。

调用规则:

  1. 调用方为一次逻辑生成分配 1-128 字节、无空格的可打印 ASCII Idempotency-Key
  2. 服务端以 owner、job kind、幂等键和规范请求建立稳定去重身份;同一请求的传输重试必须复用原键。
  3. 202 响应通过 Location / statusUrl 指向 /api/external/v1/generations/{operationId},并提供 Retry-After / pollAfterMs
  4. queued/running 返回 phase、进度与下一次建议轮询间隔;completed 返回 resultfailed 返回脱敏 error。调用方自己的轮询超时不改变任务状态。
  5. result 只保留稳定 objectKeyresourceIdassetIdassetObjectId、尺寸、媒体类型、taskId、warning 等轻量引用;禁止持久化完整 project/canvas、大型布局快照、Data URL、Blob URL、过期 signed URL、worker lease/fencing 字段和内部 provider 诊断。
  6. 需要完整项目或素材库状态时,调用方在 completed 后重新读取项目或素材库;需要下载媒体时,用稳定 objectKey/assets/read-url 获取短期签名 URL。

结果查询使用 API Key owner 过滤。任务不存在、已删除或属于其他 owner 时统一返回 404,不能通过差异错误枚举他人 operationId。

托管远程 MCP

/api/external/v1/mcp 是 Genarrative 托管的远程端点,Agent 只需配置 URL 和现有 API Key,不安装本地 MCP server。首版兼容 MCP 2025-11-25 initialize 生命周期,使用 JSON-RPC 2.0 和 Streamable HTTP,支持 initializenotifications/initializedpingtools/listtools/callresources/listresources/read。服务端使用无协议 session 的 JSON direct 模式,不依赖 sticky session,也不把 Mcp-Session-Id 作为业务身份。

MCP transport 的 DNS rebinding 防护必须同时允许正式入口 www.genarrative.world / genarrative.world、开发入口 dev.genarrative.world 和本机开发入口;对应 HTTPS Origin 也必须与公开环境同步登记。新增公开环境域名时,必须在发布前使用该域名的真实 HostOrigin 执行 initialize 回归,不能只用 localhost 单测证明端点可用。

MCP tools 从同一份 OpenAPI operation 自动形成 snake_case 名称,并在进程内复用 External REST router,因此鉴权、scope、owner、入参、幂等、计费和结果查询契约只有一份。MCP bridge 从 operation 或 path 的 required Idempotency-Key header 参数自动推导 idempotencyKey 工具参数和转发头,不维护独立的生成 operation 白名单;因此新增异步生成 operation 时,OpenAPI 契约本身就是 MCP 幂等注册来源。MCP transport 的 Authorization 头不能代替逐次业务幂等键。工具结果使用 structuredContent;业务失败使用 isError=true 的结构化安全错误,协议不可路由时才返回 JSON-RPC error。

list_editor_projects 是项目选择工具,服务端固定以 view=summary 调用项目列表,不允许因 OpenAPI 的 REST 默认值退回完整视图。摘要逐项目只返回 projectIdtitleupdatedAt 和可空 cover,不携带 canvasviewportlayersresources 或图片正文;选定目标后再用 get_editor_project 读取完整权威状态。cover 只包含最新项目封面快照的 resourceId、稳定 objectKey、尺寸与 updatedAt,没有封面时为 null。需要展示封面时,以 objectKey 调用 /api/external/v1/assets/read-url 获取短期签名 URL;列表不得内嵌 Data URL、图片二进制或临时签名 URL,也不得把签名 URL 当作持久引用。

MCP 暴露下列稳定文本资源:

  • genarrative://external-editor/usage:关键工作流和异步轮询规则。
  • genarrative://external-editor/openapi:完整 External v1 OpenAPI。
  • genarrative://external-editor/skill:Skill 入口原文;保留首版已声明的稳定 URI。
  • genarrative://external-editor/skill/references/capability-routing.md:能力选路与场景边界。
  • genarrative://external-editor/skill/references/api-operations.md:公开 API 操作、必填字段与调用顺序。
  • genarrative://external-editor/skill/references/authentication-and-safety.md:API Key 鉴权、幂等与安全边界。
  • genarrative://external-editor/skill/references/requests-and-outputs.md:异步提交、状态轮询与 compact 结果语义。

Skill 日后新增 references/ 文档时,MCP 必须按包内相对路径逐个增加 genarrative://external-editor/skill/references/<name> resource,不得只暴露 SKILL.md 而让 Agent 无法读取其引用。当前稳定 reference 精确为上述四篇,不得声明不存在的 reference。MCP Agent 直接调用托管 tools,不下载或安装 Python CLIscripts/tests/.github/workflows/ 不作为 MCP resources。

MCP 必须始终复用 require_external_api_keyowner 从 ExternalApiPrincipal 获取,不接受请求参数伪造 owner。禁止透传内部 external_generation_job procedure、worker controller、SpacetimeDB MCP 或 lease/fencing 控制面。

MCP 缺少、格式错误或无法验证 Bearer API Key 时仍返回 HTTP 401,但不能只返回通用“未授权访问”。响应必须附带 WWW-Authenticate: Bearer realm="genarrative-external-editor",并在安全 JSON details.guide 中给出稳定 reason=MCP_AUTHENTICATION_REQUIREDaction=CONFIGURE_BEARER_API_KEYAuthorization: Bearer <tnr_sk_...> 格式、登录后前往「开发者 API Key」创建密钥、原始密钥只显示一次、不得粘贴到聊天或写入仓库、配置后重试 initialize 的结构化信息,以及公开 manifest、Skill 入口和 OpenAPI 地址。三种失败使用同一响应,不得通过文案或结构差异枚举 Key 是否存在;引导不得匿名暴露 tools、resources 或 owner 信息。agent-integration.jsonmcp.credentialSetup 同步提供 action、Header 值格式、导航标签和公开 Skill 引导地址,不编造未纳入公开契约的账户页面 URL。

Agent 集成发现与完整 Skill 包

agent-integration.json 是机器可读的统一发现入口。支持远程 MCP 的 Agent 读取其中 mcp.transport/url/authentication,通过 MCP resources 读取 Skill 入口和所需 references,直接调用 MCP tools,不安装 CLI。仅不支持 MCP,或需要在 Agent 所在机器上编排本地文件上传的调用方下载 skill.archive,核对 archiveSha256,解压后从 genarrative-external-editor-api/SKILL.md 进入。

Skill archive 必须至少包含:

  • SKILL.md
  • references/capability-routing.md
  • references/api-operations.md
  • references/authentication-and-safety.md
  • references/requests-and-outputs.md
  • scripts/genarrative_external_api.py
  • agents/openai.yaml

包由 api-server 直接从仓库同源文件构建,不能只返回光秃秃的 OpenAPI JSON,也不能把个人 API Key、环境配置或本机路径写入包。完整 skill.zip 只服务不支持 MCP 或需要本地文件编排的 Agent,不是 MCP resource catalog 的压缩包镜像。Python helper 对上层保持便利的同步函数外观,但内部必须执行“异步提交 → 保存 operationId → 按 pollAfterMs 查询 → completed 返回 result”,查询超时应保留 operationId 供后续继续,不得换键重提。

api-server 使用 include_str! 嵌入 OpenAPI 与 Skill 源文件;容器构建阶段必须同时复制 docs/openapi/.codex/skills/genarrative-external-editor-api/,不能只复制 server-rs/,否则本地 Cargo 验证虽可通过,隔离镜像构建会在编译期找不到同源资源。

管理 API Key 的登录态接口保留在站内个人中心链路,但不写入外部 OpenAPI JSON

GET    /api/profile/api-keys
POST   /api/profile/api-keys
DELETE /api/profile/api-keys/{keyId}

前端入口位于登录后个人中心的 我的 → 开发者 API Key,用于查看当前 Key、创建新 Key、复制一次性明文和撤销已创建 Key。Key 卡片中的创建时间和最近使用时间必须格式化为 YYYY-MM-DD,不得直接展示后端时间戳。外部 OpenAPI 只描述 /api/external/v1 下可由 API Key 调用的接口,不混入登录态 API Key 管理接口。

版本与兼容策略

当前状态:v1 尚无外部存量调用方

截至 2026-08-08,已按当前线上 API Key 与调用方状态再次确认没有外部第三方存量调用方,v1 仍处于「已发布但无存量集成」阶段。本节记录的豁免只在该前提成立时有效;本次确认不自动延续到今后的 breaking change,每次仍需重新取得当日线上状态并形成明确决策。

已接受的未版本化 breaking change

2026-07-31「收紧编辑器内部处理元数据边界」从下列响应中移除了原本 requiredproviderinfo.version 保持 1.0.0,路径前缀保持 /api/external/v1

  • EditorImageGenerationResponseEditorIconSpritesheetGenerationResponseEditorVideoGenerationResponseEditorAudioGenerationResponseprovider 原为 required 且非空 string,属性整体移除,JSON key 不再出现。
  • EditorProjectResourceEditorAssetprovider 原为可选 nullable,属性移除后同样不再出现在响应中。

受影响端点为 POST /api/external/v1/editor/images/generations.../images/edits.../icon-spritesheets/generations.../ui-designs/assets/extractions.../videos/generations.../audios/sound-effects/generations.../audios/background-music/generations,以及全部引用上述两个资源 schema 的读取端点。

这是 breaking change,不是文档同步:严格反序列化的调用方(OpenAPI Generator 生成的 Java / Kotlin / C#、pydantic、serde 非 Option 字段)在 required 字段缺失时直接失败,且失败发生在服务端上线瞬间,不需要调用方做任何动作。脱敏目标本身成立,接受不升版本、不设弃用期的唯一依据是当前无存量调用方。

2026-07-31 同一豁免还覆盖了「八类生成从同步成功响应切换为 202 + operationId,新增统一查询接口」这一 breaking change。旧调用方若仍把生成 POST 响应当作媒体结果会立即失败;接受原地修改 v1 的唯一依据同样是上线前已确认没有外部第三方存量调用方。托管 MCP、集成 manifest 与 Skill archive 均为新增入口,不产生既有客户端兼容债务。

2026-08-08「收紧图片编辑主来源契约」把 POST /api/external/v1/editor/images/edits 的既有必填 sourceImageSrc 替换为新的必填 sourceReferenceId,并移除可选 sourceResourceId / assetKindinfo.version 继续为 1.0.0,路径继续为 /api/external/v1。严格客户端会因必填字段改名、旧字段被 additionalProperties: false 拒绝而立即失败,这属于本节定义的 breaking change。产品负责人已于 2026-08-08 根据当前线上 API Key 与调用方状态确认仍无外部调用方,因此明确接受本次不增加兼容字段、不新开 /v2、不设弃用期的原地变更。该豁免只覆盖本次字段替换,不得被后续 breaking change 自动引用。

豁免的失效条件

API Key 由用户在个人中心自助发放,因此「无外部调用方」不是受控状态,可能在无人决策的情况下变为假。本节豁免在下列任一条件出现后立即失效:

  • external_api_key 出现属于非内部账号的活跃密钥;
  • 对外公布 v1 契约、接入文档或示例代码;
  • 与外部团队开始基于 v1 的联调。

正式对外发放第一个外部密钥之前,必须先确认下节规则已生效且 v1 契约已冻结。

今后的变更规则

下列改动视为 breaking,不得在同一 info.version 与同一路径前缀下直接发布:

  • 删除响应字段,或把响应字段移出 required
  • 收窄字段类型、取值枚举或长度约束;
  • 在不改字段名的前提下改变字段语义;
  • 新增请求必填字段,或收紧既有请求字段的校验。

存量调用方出现后,上述改动按以下顺序择一处理:保留字段并返回兼容值(可为 null 或占位值);标注 deprecated 并公告弃用期后再移除;或开设 /api/external/v2 并冻结 v1。仅更新 docs/openapi/genarrative-external-v1.openapi.json 不构成合规的变更流程。

鉴权

外部调用使用 Bearer API Key

Authorization: Bearer tnr_sk_xxx

规则:

  • API Key 归属于 owner_user_id,外部接口只能访问该账号自己的项目、画布和生成素材。
  • 明文 Key 只在创建接口返回一次,后端只保存 key_hashkey_prefix
  • API Key 被撤销后立即不可再用于外部接口。
  • 外部 API 鉴权不复用登录态 JWT,不检查 refresh session;它是独立开发者凭据。
  • OpenAPI JSON、Agent 集成 manifest、原始 Skill 入口和完整 Skill archive 公共可读,不需要鉴权;MCP 与全部业务操作需要鉴权。

数据模型

新增 SpacetimeDB private 表:

external_api_key

字段:

  • key_id:主键。
  • owner_user_id:所属账号。
  • name:用户可识别名称。
  • key_prefix:前缀片段,用于列表展示和排障。
  • key_hash:完整 Key 的 SHA-256 十六进制摘要,唯一。
  • scopes_json:作用域 JSONv1 固定包含 editor:projecteditor:canvaseditor:image-generateeditor:asset。其中 editor:image-generate 覆盖图片生成、重绘、去背景、规范图、宣发图、图标拆分、UI 素材拆分、角色动画、视频、音效和音乐生成。
  • created_at / last_used_at / revoked_at / updated_at

SpacetimeDB procedure

  • create_external_api_key_and_return
  • list_external_api_keys_and_return
  • revoke_external_api_key_and_return
  • authenticate_external_api_key_and_return

LLM Router 账号 Key 与 Router 代理

本次 AGC Router 需求不改造原有 external_api_key。Router 账号、API Key 核心字段、生命周期和加密凭据统一保存在 llm_router_account;明文凭据只在 api-server 进程内短暂存在,并按 owner + route 进行 10 分钟内存缓存,轮换或撤销时立即清理。

普通 AGC 发行版不把 Router 当作客户端可配置 Provider,也不把 Router API Key 下发到桌面端。注册成功视为账号已有可用余额;认证成功后,api-server 异步尽力准备该用户对应的 Router 账号和 API KeyRouter 控制面故障不得阻塞主站登录;LLM 请求解析阶段只读取 llm_router_account 中合法的已完成 provisioning 凭据。当前按 New API 管理接口执行正式 provisioning:由于 usernamepassworddisplay_name 均限制 20 个字符,用户名固定为 agc_user_ 加 11 位 URL-safe SHA-256 短码,密码为基于完整 owner user_id 与部署侧受保护 provisioning secret 稳定派生的 20 位 hex,展示名与短用户名一致;完整 owner user_id 写入 New API 用户 remark,本地 llm_router_account.owner_user_id 仍是平台权威映射。服务端先查询远端用户:已存在则直接登录,不重复注册;确认不存在时才由管理员创建普通用户,查询用户 ID,设置 remark=<完整 owner user_id> 与用户分组 taonier,再登录、查询并复用固定标识 agc_auto_generate 的 TokenToken/API Key 固定使用 default 分组;已有固定 Token 若分组不是 default,登录恢复时先通过 Token 更新接口纠正),签发 API Key。每次新建或准备 API Key 时,服务端在签发前查询该 Router 用户的固定套餐 plan_id=1;无 active 订阅、订阅已过期或剩余时间不超过 24 小时时调用管理员订阅接口新建一条订阅,剩余超过 24 小时则复用现有订阅。订阅查询/创建只使用 api-server 私有管理员 Token,不进入客户端或 Router Key;检查锚点是显式 Router Key 准备接口和新 Key provisioning,不放在 Responses 流式 chunk 中。由于 Router 公共而各部署数据库独立,所有能操作同一 Router 的部署必须使用相同的 provisioning secret。若任一步外部结果不确定,记录进入 reconciliation 状态,禁止重复注册;本地 API Key 写入失败则保留 key_issued 状态并用确定的 key id 重试落库。Router 密文加密优先使用 GENARRATIVE_LLM_ROUTER_API_KEY_ENCRYPTION_SECRET;缺省时使用带域分离的 GENARRATIVE_JWT_SECRET 派生密钥。Router Key 的明文只在 api-server 进程内短暂存在;缓存命中时不访问数据库,缓存未命中时从 llm_router_account 解密并写入 10 分钟进程内缓存;/api/profile/api-keys/llm-router 只返回安全元数据;普通 External Editor Key 仍沿用创建接口明文只显示一次的正式链路。后续请求链路固定为:

AGC loopback Provider ProxyBearer=平台 access token
  -> POST /api/llm/responses
  -> api-server 从 access token 得到 userId,查询并解密 llm_router_account
  -> POST https://router.genarrative.world/v1/responsesBearer=<router-api-key>

/api/llm/responses 透传 Responses JSON/SSE 响应;模型由当前 AGC 模型目录解析。计费读取 Router 子账号的累计 used_quota,按 50000 quota = 1 泥点 在调用前后同步;首次同步只建立历史基线,扣钱包、写 llm_router_consume 流水与推进 checkpoint 在同一事务完成。Router 明确返回 401/403 时,服务端将该用途 Key 标记撤销;网络超时或 provisioning 未取得确定响应时不自动重试。客户端不保存 Router Key,也不把它放进 argv、环境变量、manifest、trace、聊天或普通 IPC。AGC 的运行状态接口与 /llm-status/llm-routes 只返回登录/账号凭据状态、官方路由锁定状态和运行参数,不返回 Router 地址、模型、协议名称或任何凭据字段。

Windows 私有路径由同一套正式准备入口复用:AGC 自有 AppData、凭据目录和 .agent 运行态使用 managed 范围;原生文件选择器明确选中的项目根/文件使用 user-selected 范围。两类入口在发现 owner/DACL 权限不足时均允许一次性 UAC helperhelper 只接管严格复核后的普通文件/目录,并写入当前 TokenUser owner、禁止继承且仅含当前用户 ACE 的 DACL。reparse/symlink、非普通对象、祖先类型冲突和未经过正式选择/项目根入口的路径仍失败关闭。

管理员 Token 仅由 api-server 私有配置 GENARRATIVE_LLM_ROUTER_ADMIN_TOKEN 或对应文件提供,不进入客户端、数据库、日志或请求 payload。当前仓库无法验证真实管理员 Token、真实账号 Key 与生产 Router /v1/responses 联通;这些仍需在受控部署 smoke 中完成,客户端不得退回手工 Key。

运行环境隔离是 provisioning 的前置门禁:所有环境都允许使用官方固定 Router 地址,开发环境因此可以通过完整 owner user_id 派生的稳定短用户名、稳定密码和 agc_auto_generate Token 复用共享 Router 账号;完整 owner user_id 同步写入 New API 用户 remark,便于 Router 侧统计回溯平台账号。非官方公网地址仍被拒绝,loopback 地址仅用于本地 fixture。使用共享官方 Router 的开发环境会实际创建/使用线上账号与额度,启动时必须告警。测试代码也不再通过进程级 fallback Key 旁路数据库;只有显式、按 owner 绑定的“已完成 provisioning” fixture 才能模拟已有账号,未提供 fixture 时必须经过 llm_router_account 读取或正式 provisioning,不能访问 Router。数据库记录缺失时,稳定短用户名/密码先尝试恢复远端账号;只有确认远端不存在才注册,避免跨部署数据库为空时重复创建用户。

素材生成与落库

外部生成接口复用站内编辑器已有 DTO、入队器和 worker executor,不维护第二套生成语义:

  • 图片生成 / 重绘 / 去背景 / 规范图 / 宣发图 / UI 设计图复用站内编辑器的校验、模型归一、计费和持久化规则,但 External handler 固定只入队。External v1 图片修改必须提交当前账号已登记的项目资源 ID 或素材 ID 作为 sourceReferenceId;上传对象必须先登记为项目资源或素材。objectKey、URL、Data URL、Blob URL 以及旧 sourceImageSrc/sourceResourceId/assetKind 字段均返回 400。服务端分别按资源 ID 与素材 ID 主键窄查,双表冲突、未命中、越权或对象记录无效均失败关闭,快速编辑的完整有效类型白名单为 null / spec / character / icon-spritesheet / icon-spec / publication-material / ui-design / sceneOpenAPI 的 x-genarrative-allowed-effective-asset-kinds 必须与后端白名单精确一致。请求带 targetLayerId 时必须同时带 projectId;目标图层必须关联有效项目资源,来源与目标优先比较 assetObjectId,缺失时比较 canonical (bucket, objectKey),来源默认类型还必须与目标资源默认类型一致。入队载荷保存版本化权威快照,worker 执行前再次定点解析并拒绝身份或类型漂移;仅以素材 ID 编辑时不伪造项目资源关系。主站和 External 的通用图片入口共用场景专用合同边界校验,禁止用 kind = sceneassetKind = scene 绕过后端场景 Prompt 组装;场景专用 handler 自己构造规范请求,不受该通用入口校验影响。External v1 去背景接受稳定 objectKey、项目资源 ID 或素材 ID,入队前按当前 owner 解析并规范化为权威 objectKey,同时从项目资源或素材库读取权威语义类型,并只把资产对象存储类型用于非静态媒体门禁;显式项目资源 ID / 素材 ID 优先于 objectKey 匹配,纯 objectKey 对应多条且权威元数据不一致时返回 400 并要求 sourceResourceId 或业务 ID 消歧,禁止依赖项目列表顺序。请求 assetKind 与权威语义类型冲突,或任一记录表示视频、音频、动画、图片序列时返回 400,最终队列载荷只保存服务端解析出的静态语义类型。提供 targetLayerId 时始终必须同时提供 projectId;存在 canvasCompletion 时按生成完成链路写入画布,targetLayerId 不参与原位替换;没有 canvasCompletion 时,目标图层必须存在并关联当前项目静态图片资源,并与来源优先按 assetObjectId、缺失时按 canonical (bucket, objectKey) 证明为同一对象,默认权威类型也必须一致,否则在入队前返回 400;纯 objectKey 省略 sourceResourceId 时自动绑定目标图层资源,并把绑定写入队列供 Worker 再验证。两种画布字段都未提供时只持久化请求指定的项目资源或素材记录,不自动写入画布。URL、未登记或越权引用、无效目标均在入队前失败。外部 DTO 不暴露内部 taskId,也不接受 OpenAPI 未声明字段;任务 ID 只能由服务端队列生成。
  • 图标 spritesheet 和 UI 设计图素材提取复用站内拆分逻辑,生成图集后按连通域切片,并把图集与切片都按请求写入项目资源和素材库。
  • 角色动画、视频、音效和背景音乐复用站内编辑器生成链路;请求携带 assetFolderId 时按站内规则写入素材库,音频类外部调用使用 API Key 所属账号作为 asset owner。
  • API Key 管理接口仍只属于登录态个人中心,不进入外部 OpenAPI JSON。

素材外部生成由 worker 成功后:

  1. 通过 OSS / asset object adapter 持久化媒体文件。
  2. 写入 editor_asset,让生成素材进入账号级素材库。
  3. 如果请求带 projectId,写入 editor_project_resource
  4. result_payload_json 保存 compact 稳定引用,由查询接口返回素材 ID、资源 ID、objectKey、尺寸、媒体类型、model、taskId 和 warning;普通 External API 响应、项目资源与素材 read model 不返回生成 provider、原始 prompt 或内部抠图审计字段。同源画布前端自动提交默认 segModel 属于站内 BFF 请求契约,不因此向 External OpenAPI 开放该字段。

如果请求未带 projectId,只生成并写入素材库;调用方可随后创建项目或自行保存画板布局。

素材与项目资源操作

外部素材操作复用站内图片画布素材库和项目资源的后端事实源:

  • 外部上传素材时先调用 POST /api/external/v1/assets/direct-upload-tickets 获取 OSS 表单直传参数,上传完成后调用 POST /api/external/v1/assets/objects/confirm 写入 asset_object,再用 assetObjectId / objectKey 创建账号级素材或项目资源记录。
  • 外部读取私有 generated / uploaded 素材预览时调用 GET /api/external/v1/assets/read-url 获取短期签名 URL;不开放浏览器 STS 写权限。
  • 素材库读取、文件夹创建 / 更新 / 删除、素材创建 / 更新 / 删除、项目画布资源创建全部通过 api-server -> spacetime-client -> spacetime-module
  • 所有外部素材接口使用 API Key 所属的 owner_user_id,不能由请求体传入 owner。
  • 素材库仍是账号级事实源,不归属于单个项目;项目画布内资源继续使用 editor_project_resource
  • 素材创建和项目资源创建接口只保存记录和元数据;图片二进制上传、签名读取和生成图持久化继续复用已有资产 / 生成链路。

OpenAPI 导出

OpenAPI 3.1 JSON 固定落在:

docs/openapi/genarrative-external-v1.openapi.json

服务端 GET /api/external/v1/openapi.json 和 MCP OpenAPI resource 使用同一份 JSON,通过 include_str! 导出,避免运行时生成结果与仓库文档漂移。MCP tool catalog 同样以这份 OpenAPI 的 path、method、operationId、参数和 request body 是否存在为来源;字段级精确约束继续以 OpenAPI resource 为准。

验收

  • API Key 创建只返回一次明文,列表不返回明文。
  • 撤销后的 API Key 调用外部接口返回 401
  • 九类外部生成 POST 缺少或携带非法 Idempotency-Key 时返回 400;同一 owner、请求和 key 重试只得到同一 operation。
  • External 通用图片生成携带 kind = sceneassetKind = scene 时均返回 400,且不得产生入队尝试。
  • 九类外部生成 POST 固定返回 202,查询能从 queued/running 收敛到 completed/failed;调用方超时后使用原 operationId 继续查询。
  • 外部图片生成、重绘、去背景、图标拆分、UI 素材拆分、角色动画、视频、音效和音乐 completed 后,生成结果按请求同时出现在画布资源和账号级素材库。
  • 角色图、图标 spritesheet 和 UI 素材提取的 completed result 允许携带 EditorGenerationWarning;provider 原图保留降级与自动拆分降级必须保持成功状态,并分别使用通用 warning 与兼容 sliceWarning 表达。
  • 外部视频、角色动画、音效和音乐接口使用站内编辑器相同的请求校验、模型限制和价格校验。
  • OpenAPI JSON 能被 serde_json 解析,且 security scheme 为 Bearer API Key。
  • 项目列表 REST 默认 view=full 并保持完整响应兼容;view=summary 只返回项目选择元数据和可空封面稳定引用,MCP list_editor_projects 固定使用该摘要视图,不因完整项目数据量增长触发返回体上限。
  • 摘要封面不内嵌图片或签名 URL;使用 cover.objectKey/assets/read-url 后才能临时展示。
  • OpenAPI JSON 不包含 /api/profile/api-keysUserAccessToken 或 API Key 管理 schema。
  • agent-integration.json 能发现 MCP、OpenAPI、Skill entry/archive;下载 archive 的 SHA-256 与 manifest 一致,ZIP 包含 SKILL.md、四篇 references、Python helper 和 agents/openai.yaml 七个声明文件且不含凭据。
  • MCP 在无 Bearer、Bearer 格式错误或 Key 无效时返回相同的 401 + WWW-Authenticate + details.guide 鉴权引导,且不暴露 tools/resources/owner;合法 Key 可完成 initialize、tools/list、resources/list/read 和生成提交/查询;resource catalog 必须包含 usage、OpenAPI、skill 主入口和当前全部 Skill references,当前精确为 skill/references/capability-routing.mdskill/references/api-operations.mdskill/references/authentication-and-safety.mdskill/references/requests-and-outputs.md,且不包含 CLI 脚本、测试或 workflow;多实例不依赖 sticky session,不暴露内部 SpacetimeDB MCP 或 worker 控制面。
  • 外部素材库接口覆盖当前已有素材操作:直传凭证、素材对象确认、签名读取、读取素材库、创建 / 更新 / 删除文件夹、创建 / 更新 / 删除素材、创建项目画布资源。
  • 外部项目接口覆盖当前已有项目管理操作:项目列表、最近项目、创建、读取、重命名、删除和默认画布保存。
  • 外部素材生成接口覆盖当前已有编辑器素材操作:生图、重绘 / 调整、规范图生成、宣发素材生成、图标素材生成与拆分、UI 设计图生成与拆分、角色动画、视频、音效和背景音乐。
  • 修改 SpacetimeDB schema 后运行 npm run spacetime:generatenpm run check:spacetime-schema

2026-09-05 修订:认证成功后的 Router provisioning 为异步尽力修复,不阻塞主站登录;LLM 请求只使用本地已完成的账号密钥。LLM Router 计费改为累计 used_quota checkpoint 结算,不再依赖单次响应 usage、客户端幂等键或请求指纹;账号密码派生根仅从部署侧受保护 secret/file 读取。