接入 External v1 抠图工具
Project CI / Frontend tests (push) Successful in 4m34s
Project CI / Repository checks (push) Successful in 4m38s
Project CI / Backend tests (push) Successful in 5m24s
Project CI / Native shell tests (push) Successful in 14m14s

将 agc_remove_background 切换到 External v1 异步契约

补齐稳定幂等身份、画布目录上下文与有界队列投影

同步 Skill、技术方案与项目决策记录
This commit is contained in:
2026-08-24 15:16:02 +08:00
parent 03add97d30
commit c59b8d4a1e
5 changed files with 16 additions and 25 deletions
@@ -12,4 +12,4 @@ Do not collapse these facts. A playable file can exist before projection refresh
`agc_create_or_derive_resource` accepts only semantic intent. The client resolves `sourceLocalAssetId`, creates stable request identities, recovers matching pending operations, serializes paid submissions, writes supported media into the current canvas and same-name asset folder, validates downloaded bytes, commits the local manifest transaction, and returns redacted warnings. A tool error or timeout is not permission to generate again with a new identity.
`agc_remove_background` is the account-scoped image post-processing path. It accepts only a registered image `sourceLocalAssetId` and output name; the client resolves the formal source resource, canvas/folder context, task identity, and authenticated `/api/editor/images/background-removals` call. Its result is bounded queue state; Codex must not poll internal workers, construct source URLs, or retry with a new task identity after an uncertain response.
`agc_remove_background` is the semantic image post-processing path. It accepts only a registered image `sourceLocalAssetId` and output name; the client resolves the formal source resource, canvas/folder context, stable operation identity, idempotency key, and authenticated External v1 `/api/external/v1/editor/images/background-removals` call. Its result is bounded queue state; Codex must not poll internal workers, construct source URLs, or retry with a new identity after an uncertain response.
@@ -94,7 +94,7 @@
"agents/openai.yaml",
"references/projection-contract.md"
],
"sha256": "9b59936000a4b1d69a66bfa132dccac0d972dfcbba4ad130aae6e9f8cc7d35be"
"sha256": "07d64f118dc1ecb36c37b92f272cf237649aa7f53fb939197e705d6afa2de315"
}
]
}
@@ -1328,30 +1328,24 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val
.ok_or_else(|| "图片资源缺少可供抠图服务使用的正式 resourceId".to_string())?
.to_string();
let (api_base_url, api_key, session) = resolve_canvas_sync_api_credentials(None, None)?;
if session.is_none() {
return Err("authentication-required: 抠图工具需要当前登录账号".to_string());
}
let context = prepare_external_canvas_generation_context(
&state.root,
&reqwest::Client::new(),
&ExternalEditorBindingAccess::new(&api_base_url, &api_key, session.as_ref())?,
)
.await?;
let access = ExternalEditorBindingAccess::new(&api_base_url, &api_key, session.as_ref())?;
let client = reqwest::Client::new();
let context =
prepare_external_canvas_generation_context(&state.root, &client, &access).await?;
let fingerprint = format!("{}\0{}", source_asset_id, asset_name);
let (task_id, _) = state.resource_request_ids(&fingerprint)?;
let route =
resolve_platform_editor_api_route("/api/external/v1/editor/images/background-removals");
let response = reqwest::Client::new()
let (_operation_id, idempotency_key) = state.resource_request_ids(&fingerprint)?;
let route = "/api/external/v1/editor/images/background-removals";
let response = client
.post(format!("{}{}", api_base_url, route))
.bearer_auth(api_key)
.header("Idempotency-Key", idempotency_key)
.json(&json!({
"sourceImageSrc": source_resource_id,
"projectId": manifest.project_id,
"assetKind": "background-removal",
"assetKind": source_asset.kind,
"assetFolderId": context.asset_folder_id,
"assetLabel": asset_name,
"sourceResourceId": source_resource_id,
"taskId": task_id,
}))
.send()
.await
@@ -1364,10 +1358,7 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val
if !status.is_success() {
return Err(format!("抠图服务提交失败:HTTP {}", status.as_u16()));
}
let queue_state = external_editor_response_data(&payload)
.get("queueState")
.cloned()
.ok_or_else(|| "抠图服务响应缺少 queueState".to_string())?;
let queue_state = external_editor_response_data(&payload).clone();
Ok::<_, String>(json!({
"status": "queued",
"sourceLocalAssetId": source_asset_id,
@@ -14412,5 +14412,5 @@
- Direct 对话恢复从新到旧扫描全部合法 User 回合,遇到较新已回答回合继续向前,不得丢失更早未回答回合。成功返回时 Rust 已先持久化 assistant,前端冗余 append 失败也不得重跑 Provider;普通错误终态的显式 append 失败后,恢复 claim 必须保持到 React fallback writer 对同一稳定 assistant messageId 的写入明确成功或失败,不能在 writer 尚在途时按旧 `/history` 快照重跑。fallback 成功后释放 claimfallback 失败时跳过该 writer 的无界迟到重试并释放 claim,后续显式 `/history` 才可复用原稳定 `clientTurnId`。终态收敛后删除 claim,避免长会话无界增长。
# 2026-08-24 AGC Direct 抠图语义工具
- 决策:将账号态 `/api/editor/images/background-removals` 通过 `agc_remove_background` 加入受控 `agc_tools`。工具只接受当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端负责正式 resourceId、账号态、画布/素材目录、taskId、权限和错误脱敏,不向 Codex 暴露内部 BgFilter worker、凭据或任意 API。
- 约束:该能力不是 External v1 契约,不扩展 External v1 OpenAPI;异步结果只投影有界 `queueState`,不允许模型自行构造源 URL 或在不确定提交后更换 taskId 重试
- 决策:将 External v1 `/api/external/v1/editor/images/background-removals` 通过 `agc_remove_background` 加入受控 `agc_tools`。工具只接受当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端负责正式 resourceId、画布/素材目录、稳定 operation/idempotency 身份、权限和错误脱敏,不向 Codex 暴露内部 BgFilter worker、凭据或任意 API。
- 约束:异步结果只投影有界队列状态,不允许模型自行构造源 URL 或在不确定提交后更换请求身份;External v1 负责 API Key、幂等接收与统一 operation 查询,客户端不得绕过该契约
@@ -6,7 +6,7 @@
- 项目路径、projectId、当前 revision、源文件路径与媒体类型、operationId、Idempotency-Key、登录态、项目锁、付费提交、轮询恢复、下载校验与 manifest 事务全部由客户端持有。模型不能提交或覆盖这些字段。同一 Direct `clientTurnId + 规范语义参数` 生成稳定 UUID v4 身份;单回合同参重试复用原 operation,不同请求串行且最多四项。跨回合存在完全匹配的 pending 账本时优先恢复原 operation,不能换键重发。
- 资源查询同时投影未完成 operation 的安全状态。媒体工具成功只返回 operation、本地相对路径、资源类型、Canvas/resource/asset/task 身份、正式序列帧以及脱敏后的 `warnings / sliceWarnings`;错误继续使用统一脱敏边界。客户端资源账本持久化 completed 结果的两类告警,committed replay 不能把历史告警伪装成空集合。
- 角色动画、视频、音效和背景音乐在构造新的远端请求前统一准备当前项目同名画布与素材目录上下文,并在端点支持时携带 `projectId / assetFolderId / canvasCompletion`。角色动画 placeholder 使用源图片真实宽高,避免非方形角色进入画布时失真;正式 resource/asset 与序列帧继续直接复用 External 返回身份,不从首帧伪造重复资源。已有冻结 request body 或已受理 operation 保持不变,不因本次升级重建请求或重复扣费。
- 抠图通过新增 `agc_remove_background` 语义工具开放:模型只提交当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端要求账号态,解析稳定 `resourceId`,准备同名画布/素材目录并生成受控 `taskId`,调用 `/api/editor/images/background-removals` 后只返回有界 `queueState`。抠图服务仍由客户端和服务端负责源校验、BgFilter、素材登记与画布事务,Codex 不获得内部 worker、凭据或任意 API 调用权。
- 抠图通过新增 `agc_remove_background` 语义工具开放:模型只提交当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端解析稳定 `resourceId`,准备同名画布/素材目录并生成稳定 operation/idempotency 身份,调用 External v1 `/api/external/v1/editor/images/background-removals` 后只返回有界队列状态。抠图服务仍由客户端和服务端负责源校验、BgFilter、素材登记与画布事务,Codex 不获得内部 worker、凭据或任意 API 调用权。
## 2026-08-23 AGC 资源生成补齐(视频 / 动画 / 音效 / 背景音乐)
@@ -17,7 +17,7 @@
- 新建视频 / 音效 / 背景音乐时,先复用 `prepare_external_canvas_generation_context` 创建或匹配同名画板项目与素材库目录,并在生成请求中携带 `projectId / assetFolderId / canvasCompletion`,结果同时进入平台画布 / 素材库与本地 manifest。
- durable 账本新增 `remoteLegacyPublicPath` 字段(旧账本默认缺省),用于角色动画预览视频的换签下载与恢复;资源提交、轮询、下载、manifest 提交和恢复队列继续复用既有资源编辑事务链。
- 资源编辑请求指纹纳入 `generationMode`,同一 `operationId` 换生成模式重试时不再按旧账本模式恢复;旧账本继续通过 legacy 指纹兼容。
- 去背景使用账号态客户端语义工具,不伪装成 External v1 生成契约;External v1 仍不直接开放该内部服务
- 去背景通过 External v1 语义工具开放;原始服务端 worker 和账号态内部路由仍不直接暴露给 Codex
## 2026-08-19 UI Editor 节点右键菜单