Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47d7c55058 | |||
| 893c5cf832 | |||
| 1ea5811788 | |||
| 75623207a6 | |||
| b0c5c43956 | |||
| 11afc65905 |
+1
-1
@@ -14,4 +14,4 @@ Read scopes remain separate: `asset.list` is the current project's local manifes
|
||||
|
||||
`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` accepts a registered image `sourceLocalAssetId`, `assetName`, and optional `backgroundMode` and `screenColor`. `complex` uses semantic segmentation to identify the foreground; `flat` removes a solid-colour background. Prefer `flat` when the background is known to be solid; omitting the mode selects `complex`. Only `flat` accepts a colour: `auto`, `#RRGGBB`, or omitted for automatic service detection. Never infer a concrete colour for `auto`. Empty or invalid values and colour without `flat` are rejected. 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. Mode and colour are part of request identity. 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.
|
||||
`agc_remove_background` accepts a registered image `sourceLocalAssetId`, `assetName`, and optional `backgroundMode` and `screenColor`. `complex` uses semantic segmentation to identify the foreground; `flat` removes a solid-colour background. Prefer `flat` when the background is known to be solid; omitting the mode selects `complex`. Only `flat` accepts a colour: `auto`, `#RRGGBB`, or omitted for automatic service detection. Never infer a concrete colour for `auto`. Empty or invalid values and colour without `flat` are rejected. The client resolves the formal source resource, canvas/folder context, stable operation identity, idempotency key, and authenticated request. Ordinary account mode maps the External v1 shaped route to `/api/editor/images/background-removals`; ExternalDeveloper mode uses `/api/external/v1/editor/images/background-removals`. Mode and colour are part of request identity. 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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": "agc-skill-pack.v1",
|
||||
"version": "2026-08-26.17",
|
||||
"version": "2026-08-26.18",
|
||||
"skills": [
|
||||
{
|
||||
"name": "agc-game-production-workflow",
|
||||
@@ -123,7 +123,7 @@
|
||||
"agents/openai.yaml",
|
||||
"references/projection-contract.md"
|
||||
],
|
||||
"sha256": "a929c27bc5b2b0bee0b7935e5c7b04ddbab1eb1804fe196f8c2537ad040ca5b1"
|
||||
"sha256": "b6fa326e07ba48aa542fe3a1841e0fbc425656c6a41be4d575cf2e5c353a87b2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1886,7 +1886,7 @@ async fn bridge_create_or_derive_resource(
|
||||
}
|
||||
|
||||
async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Value) -> Value {
|
||||
let result = async {
|
||||
let result = with_direct_editor_api_credentials(async {
|
||||
super::direct_tools_mcp::validate_remove_background_arguments(arguments)?;
|
||||
enforce_project_permission_policy(&state.root, "canvas.asset_generate")?;
|
||||
enforce_project_permission_policy(&state.root, "asset.register")?;
|
||||
@@ -1929,25 +1929,20 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val
|
||||
screen_color,
|
||||
);
|
||||
let (_operation_id, idempotency_key) = state.resource_request_ids(&fingerprint)?;
|
||||
let route = "/api/external/v1/editor/images/background-removals";
|
||||
let mut request_body = json!({
|
||||
"sourceImageSrc": source_resource_id,
|
||||
"projectId": manifest.project_id,
|
||||
"assetKind": source_asset.kind,
|
||||
"assetFolderId": context.asset_folder_id,
|
||||
"assetLabel": asset_name,
|
||||
"sourceResourceId": source_resource_id,
|
||||
});
|
||||
if background_mode == Some("flat") {
|
||||
request_body["backgroundMode"] = json!("flat");
|
||||
}
|
||||
if let Some(color) = screen_color {
|
||||
request_body["screenColor"] = json!(color);
|
||||
}
|
||||
let route = access.api_route("/api/external/v1/editor/images/background-removals");
|
||||
let request_body = background_removal_request_body(
|
||||
&source_resource_id,
|
||||
&context.project_id,
|
||||
&source_asset.kind,
|
||||
&context.asset_folder_id,
|
||||
&asset_name,
|
||||
background_mode,
|
||||
screen_color,
|
||||
);
|
||||
let response = crate::http_client::with_agc_main_site_marker(
|
||||
client
|
||||
.post(format!("{}{}", api_base_url, route))
|
||||
.bearer_auth(api_key)
|
||||
.bearer_auth(access.bearer_token())
|
||||
.header("Idempotency-Key", idempotency_key)
|
||||
.json(&request_body),
|
||||
)
|
||||
@@ -1965,16 +1960,17 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val
|
||||
}
|
||||
return Err(format!("抠图服务提交失败:HTTP {}", status.as_u16()));
|
||||
}
|
||||
let queue_state = external_editor_response_data(&payload).clone();
|
||||
let queue_state =
|
||||
background_removal_queue_state(&payload, access.frozen_platform_session().is_some())?;
|
||||
Ok::<_, String>(json!({
|
||||
"status": "queued",
|
||||
"sourceLocalAssetId": source_asset_id,
|
||||
"assetName": asset_name,
|
||||
"projectId": manifest.project_id,
|
||||
"projectId": context.project_id,
|
||||
"assetFolderId": context.asset_folder_id,
|
||||
"queueState": bridge_safe_queue_state(queue_state),
|
||||
}))
|
||||
}
|
||||
})
|
||||
.await;
|
||||
match result {
|
||||
Ok(value) => bridge_tool_result(value.to_string(), Vec::new(), false),
|
||||
@@ -2000,6 +1996,46 @@ fn background_removal_request_fingerprint(
|
||||
}
|
||||
}
|
||||
|
||||
fn background_removal_request_body(
|
||||
source_resource_id: &str,
|
||||
remote_project_id: &str,
|
||||
asset_kind: &str,
|
||||
asset_folder_id: &str,
|
||||
asset_name: &str,
|
||||
background_mode: Option<&str>,
|
||||
screen_color: Option<&str>,
|
||||
) -> Value {
|
||||
let mut body = json!({
|
||||
"sourceImageSrc": source_resource_id,
|
||||
"projectId": remote_project_id,
|
||||
"assetKind": asset_kind,
|
||||
"assetFolderId": asset_folder_id,
|
||||
"assetLabel": asset_name,
|
||||
"sourceResourceId": source_resource_id,
|
||||
});
|
||||
if background_mode == Some("flat") {
|
||||
body["backgroundMode"] = json!("flat");
|
||||
}
|
||||
if let Some(color) = screen_color {
|
||||
body["screenColor"] = json!(color);
|
||||
}
|
||||
body
|
||||
}
|
||||
|
||||
fn background_removal_queue_state(
|
||||
payload: &Value,
|
||||
platform_account_route: bool,
|
||||
) -> Result<Value, String> {
|
||||
let data = external_editor_response_data(payload);
|
||||
if platform_account_route {
|
||||
return data
|
||||
.get("queueState")
|
||||
.cloned()
|
||||
.ok_or_else(|| "抠图服务响应缺少 queueState".to_string());
|
||||
}
|
||||
Ok(data.clone())
|
||||
}
|
||||
|
||||
fn bridge_safe_queue_state(value: Value) -> Value {
|
||||
let object = value.as_object();
|
||||
json!({
|
||||
@@ -3604,4 +3640,53 @@ mod tests {
|
||||
assert!(projection.get("error").is_none());
|
||||
assert!(projection.get("signedUrl").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_removal_queue_state_matches_authentication_route_envelope() {
|
||||
let platform_payload = json!({
|
||||
"data": {
|
||||
"queueState": {
|
||||
"operationId": "platform-operation-1",
|
||||
"status": "queued"
|
||||
}
|
||||
}
|
||||
});
|
||||
assert_eq!(
|
||||
background_removal_queue_state(&platform_payload, true).expect("platform queue state"),
|
||||
json!({
|
||||
"operationId": "platform-operation-1",
|
||||
"status": "queued"
|
||||
})
|
||||
);
|
||||
|
||||
let external_payload = json!({
|
||||
"data": {
|
||||
"operationId": "external-operation-1",
|
||||
"status": "queued"
|
||||
}
|
||||
});
|
||||
assert_eq!(
|
||||
background_removal_queue_state(&external_payload, false).expect("external queue state"),
|
||||
json!({
|
||||
"operationId": "external-operation-1",
|
||||
"status": "queued"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_removal_request_uses_bound_remote_project_id() {
|
||||
let body = background_removal_request_body(
|
||||
"editor-resource-source",
|
||||
"remote-project-1",
|
||||
"character",
|
||||
"remote-folder-1",
|
||||
"雄狮-透明底",
|
||||
Some("complex"),
|
||||
None,
|
||||
);
|
||||
assert_eq!(body["projectId"], "remote-project-1");
|
||||
assert_ne!(body["projectId"], "local-project-1");
|
||||
assert_eq!(body["assetFolderId"], "remote-folder-1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。
|
||||
> 当前口径:历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据;如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。
|
||||
|
||||
## 2026-09-17 AGC 抠图提交使用远端画布项目身份
|
||||
|
||||
- 背景:AGC 已通过本地项目 ID 建立并持久化本地项目到主站远端画布项目的绑定,但 `agc_remove_background` 提交请求仍把本地 `manifest.project_id` 放入 `projectId`;`assetFolderId` 已使用远端素材目录 ID。主站因此按项目不存在或不属于当前账号返回 404,主站抠图和 BgFilter 本身均正常。
|
||||
- 决策:抠图请求及工具回执统一使用 `prepare_external_canvas_generation_context` 返回的远端 `context.project_id`;本地 manifest 项目 ID 只用于绑定键和本地状态,不得作为主站业务请求的 `projectId`。
|
||||
- 验证:客户端定向 Rust 测试、格式、编码和 diff 检查通过;未修改主站路由或 BgFilter。
|
||||
|
||||
## 2026-09-16 抠图模式与背景色契约
|
||||
|
||||
- External v1 抠图和 AGC `agc_remove_background` 支持 `complex`(语义分割识别前景)与 `flat`(纯色背景抠图);明确纯色背景优先 flat,模式缺省仍为 complex,主站前端保持现有行为。
|
||||
@@ -8176,8 +8183,8 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
|
||||
## 2026-08-24 AGC Direct 抠图语义工具
|
||||
|
||||
- 决策:将 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 查询,客户端不得绕过该契约。
|
||||
- 决策:将抠图能力通过 `agc_remove_background` 加入受控 `agc_tools`。工具只接受当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;普通登录态使用账号鉴权的 `/api/editor/images/background-removals`,ExternalDeveloper 模式使用 External v1 `/api/external/v1/editor/images/background-removals`。客户端负责正式 resourceId、画布/素材目录、稳定 operation/idempotency 身份、权限和错误脱敏,不向 Codex 暴露内部 BgFilter worker、凭据或任意 API。
|
||||
- 约束:异步结果只投影有界队列状态,不允许模型自行构造源 URL 或在不确定提交后更换请求身份;两种路由都接收客户端稳定幂等身份,External v1 继续负责 API Key、幂等接收与统一 operation 查询,客户端不得绕过该契约。
|
||||
|
||||
## 2026-08-24 资源详情动作、空态滚动与最终图多步恢复
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ OpenAPI 与客户端工具的对外说明只描述模式用途、参数约束和
|
||||
|
||||
工具 schema、桥接参数校验和随包 `agc-client-projection` Skill/契约说明必须保持一致。模式与颜色属于请求意图,必须参与客户端幂等指纹;同一图片与名称的不同模式不能复用同一次请求。缺省 complex 且没有颜色时保留既有指纹。主站在默认值归一化之前计算 External 请求指纹,缺失的新字段不序列化,避免旧请求重放发生冲突。
|
||||
|
||||
客户端提交前建立的本地项目绑定会返回主站远端 `projectId` 与 `assetFolderId`,抠图请求必须使用这两个远端身份;本地 manifest `projectId` 仅用于绑定和本地状态,不能直接提交给主站。
|
||||
|
||||
## 实施任务
|
||||
|
||||
### 任务一:冻结 BgFilter 契约
|
||||
|
||||
@@ -136,7 +136,7 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm
|
||||
- 项目路径、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`,准备同名画布/素材目录并生成稳定 operation/idempotency 身份,调用 External v1 `/api/external/v1/editor/images/background-removals` 后只返回有界队列状态。抠图服务仍由客户端和服务端负责源校验、BgFilter、素材登记与画布事务,Codex 不获得内部 worker、凭据或任意 API 调用权。
|
||||
- 抠图通过新增 `agc_remove_background` 语义工具开放:模型只提交当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端解析稳定 `resourceId`,准备同名画布/素材目录并生成稳定 operation/idempotency 身份。普通登录态使用账号鉴权的 `/api/editor/images/background-removals`,ExternalDeveloper 模式使用 External v1 `/api/external/v1/editor/images/background-removals`;两者都只返回有界队列状态。抠图服务仍由客户端和服务端负责源校验、BgFilter、素材登记与画布事务,Codex 不获得内部 worker、凭据或任意 API 调用权。
|
||||
|
||||
## 2026-08-23 AGC 资源生成补齐(视频 / 动画 / 音效 / 背景音乐)
|
||||
|
||||
|
||||
@@ -6266,17 +6266,19 @@ pub(crate) async fn edit_editor_image_for_owner_with_source_snapshot(
|
||||
|
||||
pub async fn remove_editor_image_background(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
Json(payload): Json<EditorBackgroundRemovalRequest>,
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
let caller = EditorGenerationCaller::from_authenticated(&authenticated);
|
||||
let idempotency_key = optional_editor_idempotency_key(&headers)?;
|
||||
let queue_job = enqueue_editor_background_removal_for_owner(
|
||||
&state,
|
||||
&request_context,
|
||||
&caller,
|
||||
payload,
|
||||
None,
|
||||
idempotency_key,
|
||||
)
|
||||
.await?;
|
||||
Ok(json_success_body(
|
||||
|
||||
Reference in New Issue
Block a user