删除客户端每回合四项媒体资源请求上限 (#427)
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 7m10s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m28s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m32s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m48s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m1s
Project CI / AI game creator shell Rust crates (push) Successful in 1m58s
Project CI / Repository checks (push) Successful in 5m46s
Project CI / Frontend tests (push) Successful in 7m8s
Project CI / Backend tests (push) Successful in 9m19s
Project CI / Native shell tests (push) Successful in 9m42s
Project CI / AI game creator shell web tests (push) Successful in 5m5s

- direct_tool_bridge.rs 删除 DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN 常量、回合授权状态的 resource_request_ids 计数 map 与 resource_request_ids() 方法
- agc_create_or_derive_resource(含 agc_edit_image 委托)与 agc_remove_background 统一按回合身份与请求指纹确定性派生 operation/idempotency id,同指纹重试复用与 pending 对账语义不变
- 付费提交串行仍由 resource_generation_gate 互斥保证,成本控制交由服务端计费与泥点余额兜底
- 决策记录新增 2026-09-19 删除上限条目,2026-09-18 抠图豁免条目标记为仅作追溯
- 实施计划文档同步改为「不同请求串行提交,不设每回合计数上限」

Reviewed-on: #427
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
This commit was merged in pull request #427.
This commit is contained in:
2026-09-19 01:44:48 +08:00
committed by 孔令弘
parent ea141d9f17
commit 1ce2c10107
3 changed files with 15 additions and 35 deletions
@@ -27,7 +27,6 @@ const DIRECT_TOOL_BRIDGE_SEARCH_URL: &str = "https://www.bing.com/search?format=
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_NAME_CHARS: usize = 120;
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_KIND_CHARS: usize = 80;
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_PAGE_SIZE: usize = 100;
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN: usize = 4;
const DIRECT_TOOL_BRIDGE_MAX_ACCOUNT_ASSET_ID_CHARS: usize = 512;
const DIRECT_TOOL_BRIDGE_MAX_LOCAL_ASSET_PATH_CHARS: usize = 512;
@@ -68,7 +67,6 @@ struct DirectToolBridgeActiveTurnAuthorization {
turn_id: String,
brief_sha256: Option<String>,
completed_result: Option<Value>,
resource_request_ids: BTreeMap<String, (String, String)>,
}
enum DirectToolBridgeRegenerationCall {
@@ -207,7 +205,6 @@ impl DirectToolBridgeState {
turn_id: turn_id.clone(),
brief_sha256: None,
completed_result: None,
resource_request_ids: BTreeMap::new(),
});
Ok(DirectToolBridgeTurnGuard {
state: Arc::clone(self),
@@ -637,33 +634,7 @@ impl DirectToolBridgeState {
Ok(())
}
fn resource_request_ids(&self, request_fingerprint: &str) -> Result<(String, String), String> {
let mut authorization = self
.turn_authorization
.lock()
.map_err(|_| "AGC 工具桥回合授权状态不可用".to_string())?;
let active = authorization
.active
.as_mut()
.ok_or_else(|| "当前没有客户端签发的资源生成回合身份".to_string())?;
if let Some(ids) = active.resource_request_ids.get(request_fingerprint) {
return Ok(ids.clone());
}
if active.resource_request_ids.len() >= DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN {
return Err("单个用户回合最多只能创建四项媒体资源请求".to_string());
}
let operation_id =
direct_resource_request_uuid(&active.turn_id, "operation", request_fingerprint);
let idempotency_key =
direct_resource_request_uuid(&active.turn_id, "idempotency", request_fingerprint);
active.resource_request_ids.insert(
request_fingerprint.to_string(),
(operation_id.clone(), idempotency_key.clone()),
);
Ok((operation_id, idempotency_key))
}
/// 不计费的派生请求(如抠图)不进 `resource_request_ids` 计数,只取回合身份做确定性 id 派生。
/// 取当前回合身份,媒体资源请求按回合身份与请求指纹确定性派生 operation/idempotency id。
fn active_resource_turn_id(&self) -> Result<String, String> {
let authorization = self
.turn_authorization
@@ -1903,8 +1874,11 @@ async fn bridge_create_or_derive_resource(
))
.await?
} else {
let (operation_id, idempotency_key) =
state.resource_request_ids(&request_fingerprint)?;
let turn_id = state.active_resource_turn_id()?;
let operation_id =
direct_resource_request_uuid(&turn_id, "operation", &request_fingerprint);
let idempotency_key =
direct_resource_request_uuid(&turn_id, "idempotency", &request_fingerprint);
let revision = read_game_creator_agent_runtime_project_revision(&state.root)?.revision;
let source_resource_id = source_asset
.as_ref()
@@ -2014,8 +1988,7 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val
})
.await?
} else {
// 抠图不计费且服务端秒级完成,不占每回合付费媒体请求的四项额度,不进计数 map;
// id 仍按回合身份与请求指纹确定性派生,同指纹重试与 pending 对账语义不变。
// id 按回合身份与请求指纹确定性派生,同指纹重试与 pending 对账语义不变。
let turn_id = state.active_resource_turn_id()?;
let operation_id = direct_resource_request_uuid(&turn_id, "operation", &fingerprint);
let idempotency_key =
@@ -3,8 +3,15 @@
> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。
> 当前口径:历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据;如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。
## 2026-09-19 AGC Direct 删除每回合四项媒体资源请求上限
- 背景:2026-08-24 引入的单回合四项上限以「整个 agent run(一条用户消息到回合结束)」为窗口,计数只增不减、请求完成不释放额度;autonomous 游戏构建要求 agent 不停下跑完整局,额度耗尽后的报错实际是终态,与技能的三次重试纪律冲突,现场表现为长时间无效重试。2026-09-18 先将不计费的抠图豁免,但付费 create/derive 仍受同一窗口问题影响。
- 决策:整体删除该上限机制。移除 `DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN` 常量、回合授权状态中的 `resource_request_ids` 计数 map 与 `resource_request_ids()` 方法;`agc_create_or_derive_resource`(含 `agc_edit_image` 委托)与 `agc_remove_background` 统一按回合身份 + 请求指纹确定性派生 operation/idempotency id,同指纹重试复用与 pending 对账语义不变。付费提交串行仍由 `resource_generation_gate` 互斥保证,成本控制由服务端计费与泥点余额兜底,客户端不再按回合计数设限。
- 验证方式:`cargo check`ai-game-creator-shell src-tauri)通过,203 项警告与基线一致;无测试断言该上限,未新增测试。
## 2026-09-18 AGC Direct 抠图不占每回合四项付费媒体额度
- 状态:2026-09-19 起该上限机制整体删除(见上条),本条目仅作追溯。
- 背景:2026-08-24 起 `agc_create_or_derive_resource``agc_remove_background` 共用每回合四项媒体资源请求上限。抠图服务端持久化 `generation_cost_mud_points: 0`(不计费),bgfilter 实测单张约 1 秒,上限导致一回合抠超过四张时后续请求被直接拒绝、agent 反复无效重试。
- 决策:`agc_remove_background` 不再经过 `resource_request_ids` 计数,直接按回合身份与请求指纹确定性派生 operation/idempotency id(与 map 复用结果一致),同指纹重试与 pending 对账语义不变。付费的 create/derive(含 `agc_edit_image` 委托)维持四项上限与原有报错文案,且抠图请求不再挤占其额度。
- 验证方式:`cargo check`ai-game-creator-shell src-tauri)与 `git diff --check` 通过;未新增测试。
@@ -203,7 +203,7 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm
- `agc_tools` 新增 `agc_list_registered_assets``agc_create_or_derive_resource`。前者按 `kind / assetId / offset / limit` 有界查询客户端权威 manifest,并可显式返回角色动画正式序列帧的稳定 objectKey、assetObjectId 和尺寸;结果不包含完整 manifest、prompt、model、provider route、签名 URL、宿主路径或凭据。后者只接受 `kind / mode / sourceLocalAssetId / prompt / assetName``create` 仅允许无源视频、音效和背景音乐,`derive` 必须引用当前项目已登记的 localAssetId,角色动画固定为 derive。
- `prompt` 上限按 `kind` 分别生效,且工具 schema、MCP 校验、客户端工具桥与提交校验共用同一权威口径(`resource_edit_prompt_max_chars`):背景音乐 140、音效 1900、视频与角色动画 4000、图片编辑 32000。schema 逐 kind 声明 `maxLength` 并在 `prompt` 描述里写明数字,超限必须在发起任何桥请求与付费提交之前失败并回报真实上限;`sourceLocalAssetId` 不是当前项目已登记资源时,错误文案必须直接给出 `agc_list_registered_assets``agc_list_project_files``agc_import_account_assets.localPaths` 两步后续动作。
- 项目路径、projectId、当前 revision、源文件路径与媒体类型、operationId、Idempotency-Key、登录态、项目锁、付费提交、轮询恢复、下载校验与 manifest 事务全部由客户端持有。模型不能提交或覆盖这些字段。同一 Direct `clientTurnId + 规范语义参数` 生成稳定 UUID v4 身份;单回合同参重试复用原 operation,不同请求串行且最多四项(不计费的抠图派生请求不占该额度,见决策记录 2026-09-18)。跨回合存在完全匹配的 pending 账本时优先恢复原 operation,不能换键重发。
- 项目路径、projectId、当前 revision、源文件路径与媒体类型、operationId、Idempotency-Key、登录态、项目锁、付费提交、轮询恢复、下载校验与 manifest 事务全部由客户端持有。模型不能提交或覆盖这些字段。同一 Direct `clientTurnId + 规范语义参数` 生成稳定 UUID v4 身份;单回合同参重试复用原 operation,不同请求串行提交,不设每回合计数上限(2026-09-19 起,见决策记录)。跨回合存在完全匹配的 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 身份。普通登录态使用账号鉴权的 `/api/editor/images/background-removals`ExternalDeveloper 模式使用 External v1 `/api/external/v1/editor/images/background-removals`;客户端接收异步受理后轮询任务状态,下载完成媒体并登记到本地 manifest,未知结果保留同一 operation 供恢复。抠图服务仍由客户端和服务端负责源校验、BgFilter、素材登记与画布事务,Codex 不获得内部 worker、凭据或任意 API 调用权。