接入 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,