From 221b15064fc0814177de9b21f5336bf5c050b60c Mon Sep 17 00:00:00 2001 From: Linghong Date: Wed, 2 Sep 2026 02:49:13 +0000 Subject: [PATCH] =?UTF-8?q?=E9=98=B6=E6=AE=B53=EF=BC=9A=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E4=B8=BB=E7=AB=99=E8=AF=B7=E6=B1=82=20Client=20=E5=B9=B6?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E8=BE=B9=E7=95=8C=E9=AA=8C=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将生产 Rust 主站请求 Client 创建点切换到统一 factory。 覆盖账号态和开发者 Key 态的项目、素材、上传确认、生成、编辑、轮询及换签请求。 保留签名 URL 下载、OSS 上传、Provider、搜索、loopback 和更新下载的独立 Client 边界。 增加画板同步请求捕获测试,验证主站请求带标记且签名媒体下载不带标记。 更新 Issue 226 阶段实施方案与验收计划,明确阶段 3 完成、阶段 4 待开始。 --- .../src-tauri/src/agent/direct_runtime.rs | 2 +- .../src-tauri/src/agent/direct_tool_bridge.rs | 4 +++- .../src/agent/generation/canvas_generation.rs | 4 ++-- .../src-tauri/src/assets.rs | 8 ++++--- .../src-tauri/src/commands.rs | 4 ++-- .../src/project/asset_canvas/generation.rs | 4 ++-- .../src-tauri/src/project/resource_editor.rs | 2 +- .../src-tauri/src/tests/project.rs | 23 ++++++++++++++++++- ...Issue226-AGC客户端主站请求统一标记-2026-09-01.md | 16 ++++++++++--- ...26-AGC客户端主站请求标记分阶段验收-2026-09-01.md | 13 +++++++++-- 10 files changed, 62 insertions(+), 18 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs index f81a8ec89..faafb514c 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs @@ -2484,7 +2484,7 @@ async fn recover_direct_taonier_spritesheet_read_only_at( task_id: art_spec.task_id.clone(), reference_resource_ids: Vec::new(), }; - let client = reqwest::Client::builder() + let client = crate::http_client::agc_main_site_client_builder() .timeout(std::time::Duration::from_secs(60)) .build() .map_err(|error| format!("创建陶泥儿只读资源恢复客户端失败:{error}"))?; diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs index ae2df3ff1..f3246b2a4 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs @@ -1810,7 +1810,9 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val .to_string(); let (api_base_url, api_key, session) = resolve_canvas_sync_api_credentials(None, None)?; let access = ExternalEditorBindingAccess::new(&api_base_url, &api_key, session.as_ref())?; - let client = reqwest::Client::new(); + let client = crate::http_client::agc_main_site_client_builder() + .build() + .map_err(|_| "创建抠图服务连接失败".to_string())?; let context = prepare_external_canvas_generation_context(&state.root, &client, &access).await?; let fingerprint = format!("{}\0{}", source_asset_id, asset_name); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs index ee9b32f60..b435982c0 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs @@ -2401,11 +2401,11 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at .map_err(|error| format!("{EXTERNAL_GENERATION_RESULT_UNKNOWN_PREFIX} {error}")) }) .transpose()?; - let client = reqwest::Client::builder() + let client = crate::http_client::agc_main_site_client_builder() .timeout(Duration::from_secs(60)) .build() .map_err(|error| format!("创建 External Editor HTTP 客户端失败:{error}"))?; - let submit_client = reqwest::Client::builder() + let submit_client = crate::http_client::agc_main_site_client_builder() .timeout(EXTERNAL_GENERATION_SUBMIT_TIMEOUT) .build() .map_err(|error| format!("创建 External Editor 生成提交客户端失败:{error}"))?; diff --git a/apps/ai-game-creator-shell/src-tauri/src/assets.rs b/apps/ai-game-creator-shell/src-tauri/src/assets.rs index 9580640b9..8ac6eee3a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/assets.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/assets.rs @@ -374,7 +374,7 @@ async fn create_private_external_editor_api_credentials_from_platform_session( .to_string() })?; let api_base_url = normalize_external_editor_api_base_url(&session.api_base_url)?; - let client = reqwest::Client::builder() + let client = crate::http_client::agc_main_site_client_builder() .connect_timeout(Duration::from_secs(10)) .timeout(Duration::from_secs(30)) .redirect(reqwest::redirect::Policy::none()) @@ -709,7 +709,9 @@ pub(crate) async fn sync_canvas_project_assets_at( init_local_game_project_at(root, "local-project-draft", "未命名游戏原型")?; let api_base_url = resolve_canvas_sync_api_base_url(api_base_url)?; let api_key = resolve_canvas_sync_api_key(api_key)?; - let client = reqwest::Client::new(); + let client = crate::http_client::agc_main_site_client_builder() + .build() + .map_err(|error| format!("创建画板同步客户端失败:{error}"))?; let project_url = format!( "{}{}", api_base_url, @@ -1120,7 +1122,7 @@ where if max_bytes == 0 { return Err("画板资产剩余下载预算为 0,已拒绝同步".to_string()); } - let secure_client = reqwest::Client::builder() + let secure_client = crate::http_client::agc_main_site_client_builder() .connect_timeout(Duration::from_secs(10)) .timeout(Duration::from_secs(60)) .redirect(reqwest::redirect::Policy::none()) diff --git a/apps/ai-game-creator-shell/src-tauri/src/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/commands.rs index a989aa710..55f86012f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/commands.rs @@ -3280,7 +3280,7 @@ async fn fetch_agent_editor_asset_records( resolve_canvas_sync_api_credentials(None, None)?; let access = ExternalEditorBindingAccess::new(&api_base_url, &bearer_token, frozen_session.as_ref())?; - let client = reqwest::Client::builder() + let client = crate::http_client::agc_main_site_client_builder() .connect_timeout(std::time::Duration::from_secs(10)) .timeout(std::time::Duration::from_secs(60)) .redirect(reqwest::redirect::Policy::none()) @@ -3830,7 +3830,7 @@ pub(crate) async fn import_account_editor_assets_for_agent( } let access = ExternalEditorBindingAccess::new(&api_base_url, &bearer_token, frozen_session.as_ref())?; - let client = reqwest::Client::builder() + let client = crate::http_client::agc_main_site_client_builder() .connect_timeout(std::time::Duration::from_secs(10)) .timeout(std::time::Duration::from_secs(60)) .redirect(reqwest::redirect::Policy::none()) diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs b/apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs index 6c92674d8..eb8be4c02 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs @@ -3726,11 +3726,11 @@ async fn reconcile_generation( .to_string(), ); } - let client = reqwest::Client::builder() + let client = crate::http_client::agc_main_site_client_builder() .timeout(Duration::from_secs(60)) .build() .map_err(|_| "无法创建图片生成 HTTP 客户端".to_string())?; - let submit_client = reqwest::Client::builder() + let submit_client = crate::http_client::agc_main_site_client_builder() .timeout(Duration::from_secs(35 * 60)) .build() .map_err(|_| "无法创建图片生成提交客户端".to_string())?; diff --git a/apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs b/apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs index bb8296f32..cf428e5e2 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs @@ -3098,7 +3098,7 @@ async fn prepare_remote_resource_edit( "result-unknown: 历史站内资源编辑 operation 不能由 External v1 自动重放".to_string(), ); } - let client = reqwest::Client::builder() + let client = crate::http_client::agc_main_site_client_builder() .timeout(Duration::from_secs(35 * 60)) .build() .map_err(|_| "无法创建资源编辑 HTTP 客户端".to_string())?; diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs index 1b1ecd5a6..610d85061 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs @@ -2052,7 +2052,8 @@ fn import_canvas_export_zip_copies_files_and_registers_assets() { #[tokio::test] async fn sync_canvas_project_assets_downloads_external_resources() { let root = unique_project_path(); - let base_url = spawn_mock_external_canvas_api_server(); + let (request_sender, request_receiver) = mpsc::channel(); + let base_url = spawn_mock_external_canvas_api_server_with_capture(3, Some(request_sender)); let _platform_session = crate::platform_session::install_test_platform_session( "canvas-sync-user", "test-editor-api-key", @@ -2097,6 +2098,26 @@ async fn sync_canvas_project_assets_downloads_external_resources() { assert!(agent_db.contains("\"recordType\":\"canvas.project_sync\"")); assert!(!agent_db.contains("test-editor-api-key")); + let requests = request_receiver.try_iter().collect::>(); + assert_eq!(requests.len(), 3, "画板同步应产生项目、换签和媒体请求"); + let main_site_requests = requests + .iter() + .filter(|request| !request.starts_with("GET /signed/")) + .collect::>(); + assert_eq!(main_site_requests.len(), 2); + assert!(main_site_requests.iter().all(|request| { + request + .to_ascii_lowercase() + .contains("x-genarrative-client: agc") + })); + let signed_download = requests + .iter() + .find(|request| request.starts_with("GET /signed/")) + .expect("signed media download request"); + assert!(!signed_download + .to_ascii_lowercase() + .contains("x-genarrative-client:")); + fs::remove_dir_all(root).ok(); } diff --git a/local-docs/【实施方案】Issue226-AGC客户端主站请求统一标记-2026-09-01.md b/local-docs/【实施方案】Issue226-AGC客户端主站请求统一标记-2026-09-01.md index 449122a9e..e27bc2432 100644 --- a/local-docs/【实施方案】Issue226-AGC客户端主站请求统一标记-2026-09-01.md +++ b/local-docs/【实施方案】Issue226-AGC客户端主站请求统一标记-2026-09-01.md @@ -1,13 +1,13 @@ # Issue #226:AGC 客户端主站请求统一标记实施方案 -更新时间:2026-09-01
+更新时间:2026-09-02
关联 Issue: - `#226 添加客户端特殊标识`:本方案实际实施范围 - `#225 添加客户端埋点统计`:主站接收、落库和后台统计,本文只冻结交接契约,不在本次实施 -状态:阶段 2 已完成,待阶段 3 实施
-本方案不包含代码修改 +状态:阶段 3 已完成,待阶段 4 实施
+本方案包含 #226 客户端代码修改,不包含 #225 主站接收、落库和后台统计代码 ## 1. 一句话交付结果 @@ -227,6 +227,16 @@ let client = agc_main_site_client_builder() - loopback 客户端工具桥 Client。 - [main.rs](C:/projects/narrative/Genarrative/apps/ai-game-creator-shell/src-tauri/src/main.rs) 的更新下载请求。 +### 6.5 阶段 3 实施记录 + +- 已将生产代码中明确用于主站 API 的 Client 创建点切换为 `crate::http_client::agc_main_site_client_builder()`,覆盖 `assets.rs`、`commands.rs`、`canvas_generation.rs`、`direct_runtime.rs`、`direct_tool_bridge.rs`、`asset_canvas/generation.rs` 和 `resource_editor.rs`。 +- 主站项目、素材库、素材目录、上传凭证、对象确认、资源登记、图片生成/编辑/抠图、视频/动画/音频生成以及 generation 查询请求复用带 `X-Genarrative-Client: agc` 的 Client。 +- `assets/read-url` 换签请求使用主站 factory;签名 URL 媒体下载仍使用独立的 `build_external_asset_download_client`,不会携带主站标记。 +- OSS multipart 上传、Provider、受控搜索、loopback 工具桥和更新下载没有切换到主站 factory。 +- 保留原有 timeout、connect timeout、redirect、`no_proxy`、Bearer/API Key、Idempotency-Key、请求体和响应处理;未修改 `ExternalEditorBindingAccess` 的路由映射语义。 +- 阶段 3 请求捕获验证:`sync_canvas_project_assets_downloads_external_resources`(1 test passed)确认账号态项目请求和 `assets/read-url` 带标记,签名媒体下载不带标记。 +- 阶段 3 验证命令:`cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml http_client -- --nocapture`(2 tests passed);`cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml sync_canvas_project_assets_downloads_external_resources -- --nocapture`(1 test passed);`cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check`、`npm run check:encoding` 和 `git diff --check` 均通过。 + ## 7. 调用语义和现有路由边界 本次不改变 `ExternalEditorBindingAccess` 的职责: diff --git a/local-docs/【实施计划】Issue226-AGC客户端主站请求标记分阶段验收-2026-09-01.md b/local-docs/【实施计划】Issue226-AGC客户端主站请求标记分阶段验收-2026-09-01.md index ad4ed2b44..a0ac327cf 100644 --- a/local-docs/【实施计划】Issue226-AGC客户端主站请求标记分阶段验收-2026-09-01.md +++ b/local-docs/【实施计划】Issue226-AGC客户端主站请求标记分阶段验收-2026-09-01.md @@ -1,12 +1,12 @@ # Issue #226:AGC 客户端主站请求标记分阶段实施与验收计划 -更新时间:2026-09-01
+更新时间:2026-09-02
关联 Issue: - `#226 添加客户端特殊标识`:本计划全部实施范围 - `#225 添加客户端埋点统计`:只接收交接契约,不在本计划实现 -当前状态:阶段 2 已完成;阶段 3 尚未开始。阶段 0 的证据与验收记录见 +当前状态:阶段 3 已完成;阶段 4 尚未开始。阶段 0 的证据与验收记录见 [【阶段验收】Issue226阶段0现状基线与契约冻结-2026-09-01.md](C:/projects/narrative/Genarrative/local-docs/【阶段验收】Issue226阶段0现状基线与契约冻结-2026-09-01.md)。 ## 1. 交付目标 @@ -243,6 +243,15 @@ fn agc_main_site_client_builder() -> reqwest::ClientBuilder; - 没有把所有 Rust Client 粗暴替换成主站 Client。 - 旧的第三方 Client 边界仍然清晰。 +### 7.5 阶段 3 实施记录 + +- 已迁移 7 个生产 Rust 文件中的主站 Client 创建点:`assets.rs`、`commands.rs`、`agent/generation/canvas_generation.rs`、`agent/direct_runtime.rs`、`agent/direct_tool_bridge.rs`、`project/asset_canvas/generation.rs` 和 `project/resource_editor.rs`。 +- 迁移覆盖账号态实际 `/api/editor`、`/api/assets`、`/api/runtime` 路径,以及开发者 Key 态 `/api/external/v1` 路径;`assets/read-url` 的主站换签请求也通过 factory 创建。 +- 签名 URL 下载和 OSS multipart 上传仍由独立安全下载 Client 负责;Provider、搜索、loopback 和更新下载保持裸 Client。 +- 未修改 `ExternalEditorBindingAccess` 路由映射、凭据、请求体、operationId、Idempotency-Key 或本地账本语义。 +- 请求捕获测试已验证账号态画板同步的项目请求和换签请求带 `X-Genarrative-Client: agc`,签名媒体下载不带该 Header。 +- 阶段 3 验证:`http_client` 定向测试 2 个通过;`sync_canvas_project_assets_downloads_external_resources` 通过;Rust fmt、编码检查和 `git diff --check` 通过。 + ## 8. 阶段 4:第三方请求隔离与负向验证 ### 8.1 工作内容