From f1fb92aa297c65c1d01c47547b3ffc526fa8b21a Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Thu, 28 May 2026 15:42:46 +0800 Subject: [PATCH] Enrich external API failure audit metadata --- packages/shared/src/http.ts | 28 +++++++++++++ server-rs/crates/api-server/src/admin.rs | 8 +++- server-rs/crates/api-server/src/app.rs | 3 +- .../crates/api-server/src/bark_battle.rs | 3 ++ .../api-server/src/character_visual_assets.rs | 10 +++-- .../crates/api-server/src/custom_world_ai.rs | 10 ++--- .../src/custom_world_ai/opening_cg.rs | 5 +-- .../api-server/src/external_api_audit.rs | 21 ++++++++-- server-rs/crates/api-server/src/jump_hop.rs | 6 +-- server-rs/crates/api-server/src/main.rs | 4 +- .../crates/api-server/src/match3d/handlers.rs | 3 ++ .../api-server/src/match3d/item_assets.rs | 12 ++++-- .../crates/api-server/src/match3d/works.rs | 37 ++++++++++------ .../api-server/src/openai_image_generation.rs | 14 +++++-- .../api-server/src/puzzle/generation.rs | 10 +++-- .../api-server/src/puzzle/vector_engine.rs | 12 ++++-- .../src/square_hole/visual_assets.rs | 19 +++++++-- server-rs/crates/api-server/src/state.rs | 4 +- .../crates/api-server/src/wooden_fish.rs | 3 ++ .../crates/api-server/src/work_author.rs | 10 ++++- server-rs/crates/module-auth/src/lib.rs | 28 +++++-------- server-rs/crates/spacetime-client/src/lib.rs | 11 +++-- .../crates/spacetime-client/src/mapper.rs | 5 +-- .../crates/spacetime-client/src/runtime.rs | 33 +++++++-------- .../spacetime-module/src/auth/procedures.rs | 23 +++++----- .../spacetime-module/src/custom_world.rs | 2 +- .../crates/spacetime-module/src/migration.rs | 2 +- .../crates/spacetime-module/src/runtime.rs | 4 +- .../src/runtime/admin_work_visibility.rs | 42 +++++++++++++------ .../characterAssetWorkflowPersistence.ts | 6 ++- src/editor/shared/editorApiClient.ts | 9 +++- src/editor/shared/jsonClient.ts | 20 +++++++-- src/services/aiService.ts | 19 +++++++-- src/services/apiClient.test.ts | 1 + src/services/apiClient.ts | 12 +++--- src/services/assetReadUrlService.ts | 12 +++++- .../creationAgentClientFactory.ts | 4 +- .../creative-agent/creativeAgentClient.ts | 4 +- .../rpg-creation/rpgCreationRequestHelpers.ts | 4 +- .../visualNovelRuntimeClient.ts | 4 +- 40 files changed, 315 insertions(+), 152 deletions(-) diff --git a/packages/shared/src/http.ts b/packages/shared/src/http.ts index 03448e27..f5514932 100644 --- a/packages/shared/src/http.ts +++ b/packages/shared/src/http.ts @@ -227,3 +227,31 @@ export function parseApiErrorMessage(rawText: string, fallbackMessage: string) { return rawText.trim() || fallbackMessage; } + +export function appendApiErrorRequestId( + message: string, + requestId: string | null | undefined, +) { + const trimmedMessage = message.trim() || '请求失败'; + const trimmedRequestId = + typeof requestId === 'string' && requestId.trim() + ? requestId.trim() + : ''; + + if (!trimmedRequestId || trimmedMessage.includes(trimmedRequestId)) { + return trimmedMessage; + } + + return `${trimmedMessage}(requestId: ${trimmedRequestId})`; +} + +export function parseApiErrorMessageWithRequestId( + rawText: string, + fallbackMessage: string, + requestId: string | null | undefined, +) { + return appendApiErrorRequestId( + parseApiErrorMessage(rawText, fallbackMessage), + requestId, + ); +} diff --git a/server-rs/crates/api-server/src/admin.rs b/server-rs/crates/api-server/src/admin.rs index d6637575..78a5d8dd 100644 --- a/server-rs/crates/api-server/src/admin.rs +++ b/server-rs/crates/api-server/src/admin.rs @@ -325,11 +325,15 @@ fn validate_admin_work_visibility( ) -> Result<(String, String, bool), AppError> { let source_type = payload.source_type.trim().to_string(); if source_type.is_empty() { - return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_message("sourceType 不能为空")); + return Err( + AppError::from_status(StatusCode::BAD_REQUEST).with_message("sourceType 不能为空") + ); } let profile_id = payload.profile_id.trim().to_string(); if profile_id.is_empty() { - return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_message("profileId 不能为空")); + return Err( + AppError::from_status(StatusCode::BAD_REQUEST).with_message("profileId 不能为空") + ); } Ok((source_type, profile_id, payload.visible)) } diff --git a/server-rs/crates/api-server/src/app.rs b/server-rs/crates/api-server/src/app.rs index 4b0d747c..142d4725 100644 --- a/server-rs/crates/api-server/src/app.rs +++ b/server-rs/crates/api-server/src/app.rs @@ -658,7 +658,8 @@ mod tests { #[tokio::test] async fn spacetime_unavailable_router_returns_service_unavailable_for_requests() { - let app = build_spacetime_unavailable_router("SpacetimeDB 启动恢复认证快照超时".to_string()); + let app = + build_spacetime_unavailable_router("SpacetimeDB 启动恢复认证快照超时".to_string()); let response = app .oneshot( diff --git a/server-rs/crates/api-server/src/bark_battle.rs b/server-rs/crates/api-server/src/bark_battle.rs index a2366218..f89affce 100644 --- a/server-rs/crates/api-server/src/bark_battle.rs +++ b/server-rs/crates/api-server/src/bark_battle.rs @@ -311,6 +311,7 @@ pub async fn generate_bark_battle_image_asset( async { generate_and_persist_bark_battle_image_asset( &state, + &request_context, &owner_user_id, &slot, draft_id.as_deref(), @@ -1197,6 +1198,7 @@ fn bark_battle_sanitize_path_segment(value: &str, fallback: &str) -> String { async fn generate_and_persist_bark_battle_image_asset( state: &AppState, + request_context: &RequestContext, owner_user_id: &str, slot: &BarkBattleAssetSlot, draft_id: Option<&str>, @@ -1205,6 +1207,7 @@ async fn generate_and_persist_bark_battle_image_asset( size: &str, ) -> Result { let settings = require_openai_image_settings(state)?.with_external_api_audit_context( + &request_context, Some(owner_user_id.to_string()), Some(draft_id.unwrap_or(asset_id).to_string()), ); diff --git a/server-rs/crates/api-server/src/character_visual_assets.rs b/server-rs/crates/api-server/src/character_visual_assets.rs index b1a14fac..5aa1028d 100644 --- a/server-rs/crates/api-server/src/character_visual_assets.rs +++ b/server-rs/crates/api-server/src/character_visual_assets.rs @@ -95,8 +95,12 @@ pub async fn generate_character_visual( let result = async { let settings = require_openai_image_settings(&state)? - .with_external_api_audit_context(Some(owner_user_id.clone()), Some(character_id.clone())) - .with_external_api_audit_request_id(Some(request_context.request_id().to_string())); + .with_external_api_audit_context( + &request_context, + Some(owner_user_id.clone()), + Some(character_id.clone()), + ) + ; let http_client = build_openai_image_http_client(&settings)?; state @@ -320,7 +324,7 @@ pub(crate) async fn generate_character_primary_visual_for_profile( &model, &prompt, )?; - let settings = require_openai_image_settings(state)?.with_external_api_audit_context( + let settings = require_openai_image_settings(state)?.with_external_api_audit_actor( Some(owner_user_id.to_string()), Some(character_id.clone()), ); diff --git a/server-rs/crates/api-server/src/custom_world_ai.rs b/server-rs/crates/api-server/src/custom_world_ai.rs index ac6f2d41..0aa81311 100644 --- a/server-rs/crates/api-server/src/custom_world_ai.rs +++ b/server-rs/crates/api-server/src/custom_world_ai.rs @@ -555,10 +555,10 @@ pub async fn generate_custom_world_scene_image( async { let settings = require_openai_image_settings(&state)? .with_external_api_audit_context( + &request_context, Some(owner_user_id.to_string()), normalized.profile_id.clone(), - ) - .with_external_api_audit_request_id(Some(request_context.request_id().to_string())); + ); let http_client = build_openai_image_http_client(&settings)?; let reference_image = if let Some(reference_image_src) = normalized.reference_image_src.as_deref() { @@ -680,7 +680,7 @@ pub(crate) async fn generate_custom_world_scene_image_for_profile( }), }; let normalized = normalize_scene_image_request(payload)?; - let settings = require_openai_image_settings(state)?.with_external_api_audit_context( + let settings = require_openai_image_settings(state)?.with_external_api_audit_actor( Some(owner_user_id.to_string()), normalized.profile_id.clone(), ); @@ -1021,10 +1021,10 @@ pub async fn generate_custom_world_opening_cg( async { let image_settings = require_openai_image_settings(&state)? .with_external_api_audit_context( + &request_context, Some(owner_user_id.clone()), normalized.profile_id.clone(), - ) - .with_external_api_audit_request_id(Some(request_context.request_id().to_string())); + ); let image_http_client = build_openai_image_http_client(&image_settings)?; let video_settings = require_ark_video_settings(&state)?; let video_http_client = build_upstream_http_client(video_settings.request_timeout_ms)?; diff --git a/server-rs/crates/api-server/src/custom_world_ai/opening_cg.rs b/server-rs/crates/api-server/src/custom_world_ai/opening_cg.rs index 6d99f2b7..8d3f6593 100644 --- a/server-rs/crates/api-server/src/custom_world_ai/opening_cg.rs +++ b/server-rs/crates/api-server/src/custom_world_ai/opening_cg.rs @@ -8,10 +8,7 @@ pub(super) async fn generate_opening_cg_storyboard( normalized: &NormalizedOpeningCgRequest, reference_images: &[String], ) -> Result { - let audit_settings = settings.clone().with_external_api_audit_context( - Some(owner_user_id.to_string()), - normalized.profile_id.clone(), - ); + let audit_settings = settings.clone(); let generated = create_openai_image_generation( http_client, &audit_settings, diff --git a/server-rs/crates/api-server/src/external_api_audit.rs b/server-rs/crates/api-server/src/external_api_audit.rs index 8cc82fa1..d75b3e56 100644 --- a/server-rs/crates/api-server/src/external_api_audit.rs +++ b/server-rs/crates/api-server/src/external_api_audit.rs @@ -260,13 +260,28 @@ fn build_external_api_failure_metadata(failure: &ExternalApiFailureDraft) -> Val if let Some(image_model) = failure.image_model { metadata["imageModel"] = json!(image_model); } - if let Some(user_id) = failure.user_id.as_deref().map(str::trim).filter(|value| !value.is_empty()) { + if let Some(user_id) = failure + .user_id + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + { metadata["userId"] = json!(truncate_field(user_id, 1_000)); } - if let Some(profile_id) = failure.profile_id.as_deref().map(str::trim).filter(|value| !value.is_empty()) { + if let Some(profile_id) = failure + .profile_id + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + { metadata["profileId"] = json!(truncate_field(profile_id, 1_000)); } - if let Some(request_id) = failure.request_id.as_deref().map(str::trim).filter(|value| !value.is_empty()) { + if let Some(request_id) = failure + .request_id + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + { metadata["requestId"] = json!(truncate_field(request_id, 1_000)); } if let Some(source) = failure diff --git a/server-rs/crates/api-server/src/jump_hop.rs b/server-rs/crates/api-server/src/jump_hop.rs index 04e025e7..910c18f2 100644 --- a/server-rs/crates/api-server/src/jump_hop.rs +++ b/server-rs/crates/api-server/src/jump_hop.rs @@ -416,14 +416,14 @@ async fn maybe_generate_jump_hop_assets( .map(|settings| { settings .with_external_api_audit_context( + request_context, Some(owner_user_id.to_string()), Some(profile_id.clone()), ) - .with_external_api_audit_request_id(Some(request_context.request_id().to_string())) }) .map_err(|error| { - jump_hop_error_response(request_context, JUMP_HOP_CREATION_PROVIDER, error) - })?; + jump_hop_error_response(request_context, JUMP_HOP_CREATION_PROVIDER, error) + })?; let http_client = build_openai_image_http_client(&settings).map_err(|error| { jump_hop_error_response(request_context, JUMP_HOP_CREATION_PROVIDER, error) })?; diff --git a/server-rs/crates/api-server/src/main.rs b/server-rs/crates/api-server/src/main.rs index ecd6635e..0c511311 100644 --- a/server-rs/crates/api-server/src/main.rs +++ b/server-rs/crates/api-server/src/main.rs @@ -172,7 +172,9 @@ async fn run_server(config: AppConfig) -> Result<(), io::Error> { build_spacetime_unavailable_router(message) } Err(error) => { - return Err(std::io::Error::other(format!("初始化应用状态失败:{error}"))); + return Err(std::io::Error::other(format!( + "初始化应用状态失败:{error}" + ))); } }; diff --git a/server-rs/crates/api-server/src/match3d/handlers.rs b/server-rs/crates/api-server/src/match3d/handlers.rs index b4837ec6..6d41626f 100644 --- a/server-rs/crates/api-server/src/match3d/handlers.rs +++ b/server-rs/crates/api-server/src/match3d/handlers.rs @@ -701,6 +701,7 @@ pub async fn generate_match3d_cover_image( .await?; let generated_cover = generate_match3d_cover_image_asset( &state, + &request_context, &context.owner_user_id, context.session_id.as_str(), profile_id.as_str(), @@ -772,6 +773,7 @@ pub async fn generate_match3d_background_image_for_work( async { let generated_background = generate_match3d_background_image( &state, + &request_context, owner_user_id.as_str(), session_id.as_str(), profile_id.as_str(), @@ -883,6 +885,7 @@ pub async fn generate_match3d_container_image_for_work( async { let generated_container = generate_match3d_container_image( &state, + &request_context, owner_user_id.as_str(), session_id.as_str(), profile_id.as_str(), diff --git a/server-rs/crates/api-server/src/match3d/item_assets.rs b/server-rs/crates/api-server/src/match3d/item_assets.rs index f3b04fb6..726f0c7e 100644 --- a/server-rs/crates/api-server/src/match3d/item_assets.rs +++ b/server-rs/crates/api-server/src/match3d/item_assets.rs @@ -202,6 +202,7 @@ async fn generate_match3d_item_image_assets_in_batches( async move { let material_sheet = generate_match3d_material_sheet_from_level_scene( state, + request_context, owner_user_id, session_id, profile_id, @@ -747,16 +748,19 @@ pub(super) struct Match3DSlicedItemImage { async fn generate_match3d_material_sheet_from_level_scene( state: &AppState, + request_context: &RequestContext, owner_user_id: &str, session_id: &str, profile_id: &str, config: &Match3DConfigJson, background_asset: Option<&Match3DGeneratedBackgroundAsset>, ) -> Result { - let settings = require_openai_image_settings(state)?.with_external_api_audit_context( - Some(owner_user_id.to_string()), - Some(profile_id.to_string()), - ); + let settings = require_openai_image_settings(state)? + .with_external_api_audit_context( + request_context, + Some(owner_user_id.to_string()), + Some(profile_id.to_string()), + ); let http_client = build_openai_image_http_client(&settings)?; let prompt = build_match3d_item_spritesheet_prompt(); let reference = load_match3d_level_scene_reference_image(state, background_asset).await?; diff --git a/server-rs/crates/api-server/src/match3d/works.rs b/server-rs/crates/api-server/src/match3d/works.rs index 7d64c723..99d4ef06 100644 --- a/server-rs/crates/api-server/src/match3d/works.rs +++ b/server-rs/crates/api-server/src/match3d/works.rs @@ -214,6 +214,7 @@ pub(super) async fn ensure_match3d_background_asset( let generated_background = generate_match3d_level_asset_bundle( state, + request_context, owner_user_id, session_id, profile_id, @@ -260,6 +261,7 @@ pub(super) async fn resolve_or_generate_match3d_level_asset_bundle( }; generate_match3d_level_asset_bundle( state, + request_context, owner_user_id, session_id, profile_id, @@ -292,6 +294,7 @@ pub(super) fn build_match3d_item_slug(item_id: &str, item_name: &str) -> String pub(super) async fn generate_match3d_cover_image_asset( state: &AppState, + request_context: &RequestContext, owner_user_id: &str, session_id: &str, profile_id: &str, @@ -301,10 +304,12 @@ pub(super) async fn generate_match3d_cover_image_asset( reference_image_srcs: Vec, ) -> Result { require_match3d_oss_client(state)?; - let settings = require_openai_image_settings(state)?.with_external_api_audit_context( - Some(owner_user_id.to_string()), - Some(profile_id.to_string()), - ); + let settings = require_openai_image_settings(state)? + .with_external_api_audit_context( + request_context, + Some(owner_user_id.to_string()), + Some(profile_id.to_string()), + ); let http_client = build_openai_image_http_client(&settings)?; let cover_prompt = build_match3d_cover_generation_prompt(config, prompt); let generated = if let Some(uploaded_image) = resolve_match3d_reference_image_for_edit( @@ -425,6 +430,7 @@ pub(super) fn build_match3d_cover_reference_generation_prompt( pub(super) async fn generate_match3d_background_image( state: &AppState, + request_context: &RequestContext, owner_user_id: &str, session_id: &str, profile_id: &str, @@ -433,6 +439,7 @@ pub(super) async fn generate_match3d_background_image( ) -> Result { generate_match3d_level_asset_bundle( state, + request_context, owner_user_id, session_id, profile_id, @@ -444,6 +451,7 @@ pub(super) async fn generate_match3d_background_image( pub(super) async fn generate_match3d_level_asset_bundle( state: &AppState, + request_context: &RequestContext, owner_user_id: &str, session_id: &str, profile_id: &str, @@ -451,10 +459,12 @@ pub(super) async fn generate_match3d_level_asset_bundle( prompt: &str, ) -> Result { require_match3d_oss_client(state)?; - let settings = require_openai_image_settings(state)?.with_external_api_audit_context( - Some(owner_user_id.to_string()), - Some(profile_id.to_string()), - ); + let settings = require_openai_image_settings(state)? + .with_external_api_audit_context( + request_context, + Some(owner_user_id.to_string()), + Some(profile_id.to_string()), + ); let http_client = build_openai_image_http_client(&settings)?; let level_scene_prompt = build_match3d_level_scene_generation_prompt(config); @@ -589,6 +599,7 @@ pub(super) async fn generate_match3d_level_asset_bundle( pub(super) async fn generate_match3d_container_image( state: &AppState, + request_context: &RequestContext, owner_user_id: &str, session_id: &str, profile_id: &str, @@ -596,10 +607,12 @@ pub(super) async fn generate_match3d_container_image( prompt: &str, ) -> Result { require_match3d_oss_client(state)?; - let settings = require_openai_image_settings(state)?.with_external_api_audit_context( - Some(owner_user_id.to_string()), - Some(profile_id.to_string()), - ); + let settings = require_openai_image_settings(state)? + .with_external_api_audit_context( + request_context, + Some(owner_user_id.to_string()), + Some(profile_id.to_string()), + ); let http_client = build_openai_image_http_client(&settings)?; let reference_image = load_match3d_container_reference_image()?; let container_prompt = build_match3d_container_generation_prompt(config, prompt); diff --git a/server-rs/crates/api-server/src/openai_image_generation.rs b/server-rs/crates/api-server/src/openai_image_generation.rs index 7f3ae424..4ecca8b2 100644 --- a/server-rs/crates/api-server/src/openai_image_generation.rs +++ b/server-rs/crates/api-server/src/openai_image_generation.rs @@ -16,6 +16,7 @@ use crate::{ record_external_api_failure, }, http_error::AppError, + request_context::RequestContext, state::AppState, tracking::record_external_generation_run_after_success, }; @@ -258,7 +259,7 @@ pub(crate) fn build_openai_image_request_body( } impl OpenAiImageSettings { - pub(crate) fn with_external_api_audit_context( + pub(crate) fn with_external_api_audit_actor( mut self, user_id: Option, profile_id: Option, @@ -268,8 +269,15 @@ impl OpenAiImageSettings { self } - pub(crate) fn with_external_api_audit_request_id(mut self, request_id: Option) -> Self { - self.external_api_audit_request_id = request_id; + pub(crate) fn with_external_api_audit_context( + mut self, + request_context: &RequestContext, + user_id: Option, + profile_id: Option, + ) -> Self { + self.external_api_audit_user_id = user_id; + self.external_api_audit_profile_id = profile_id; + self.external_api_audit_request_id = Some(request_context.request_id().to_string()); self } diff --git a/server-rs/crates/api-server/src/puzzle/generation.rs b/server-rs/crates/api-server/src/puzzle/generation.rs index 208b0717..a17766f7 100644 --- a/server-rs/crates/api-server/src/puzzle/generation.rs +++ b/server-rs/crates/api-server/src/puzzle/generation.rs @@ -269,10 +269,10 @@ pub(crate) async fn generate_puzzle_ui_background_image( ) -> Result { let settings = require_openai_image_settings(state.root_state())? .with_external_api_audit_context( + request_context, Some(owner_user_id.to_string()), Some(session_id.to_string()), - ) - .with_external_api_audit_request_id(Some(request_context.request_id().to_string())); + ); let http_client = build_openai_image_http_client(&settings)?; let generated = create_openai_image_generation( &http_client, @@ -311,7 +311,11 @@ pub(crate) async fn generate_puzzle_level_asset_bundle( puzzle_image: &PuzzleDownloadedImage, ) -> Result { let settings = require_puzzle_vector_engine_settings(state)? - .with_external_api_audit_request_id(Some(request_context.request_id().to_string())); + .with_external_api_audit_context( + request_context, + Some(owner_user_id.to_string()), + Some(session_id.to_string()), + ); let http_client = build_puzzle_image_http_client(state, PuzzleImageModel::GptImage2)?; let puzzle_reference = build_puzzle_downloaded_image_reference(puzzle_image); let scene_generated = create_puzzle_vector_engine_image_generation( diff --git a/server-rs/crates/api-server/src/puzzle/vector_engine.rs b/server-rs/crates/api-server/src/puzzle/vector_engine.rs index 8b7db2cb..4c6f9b2f 100644 --- a/server-rs/crates/api-server/src/puzzle/vector_engine.rs +++ b/server-rs/crates/api-server/src/puzzle/vector_engine.rs @@ -109,13 +109,19 @@ impl PuzzleVectorEngineSettings { } } - pub(crate) fn with_external_api_audit_request_id( + pub(crate) fn with_external_api_audit_context( mut self, - request_id: Option, + request_context: &RequestContext, + user_id: Option, + profile_id: Option, ) -> Self { - self.external_api_audit_request_id = request_id; + self.external_api_audit_user_id = user_id; + self.external_api_audit_profile_id = profile_id; + self.external_api_audit_request_id = + Some(request_context.request_id().to_string()); self } + } pub(crate) struct ParsedPuzzleImageDataUrl { diff --git a/server-rs/crates/api-server/src/square_hole/visual_assets.rs b/server-rs/crates/api-server/src/square_hole/visual_assets.rs index 9f6dff5f..75ad863e 100644 --- a/server-rs/crates/api-server/src/square_hole/visual_assets.rs +++ b/server-rs/crates/api-server/src/square_hole/visual_assets.rs @@ -62,6 +62,7 @@ pub(super) async fn generate_square_hole_visual_assets_for_session( _ => Some( generate_square_hole_image_data_url( state, + request_context, &owner_user_id, &session_id, profile_id.as_str(), @@ -90,6 +91,7 @@ pub(super) async fn generate_square_hole_visual_assets_for_session( _ => Some( generate_square_hole_image_data_url( state, + request_context, &owner_user_id, &session_id, profile_id.as_str(), @@ -118,6 +120,7 @@ pub(super) async fn generate_square_hole_visual_assets_for_session( option.image_src = Some( generate_square_hole_image_data_url( state, + request_context, &owner_user_id, &session_id, profile_id.as_str(), @@ -145,6 +148,7 @@ pub(super) async fn generate_square_hole_visual_assets_for_session( option.image_src = Some( generate_square_hole_image_data_url( state, + request_context, &owner_user_id, &session_id, profile_id.as_str(), @@ -252,6 +256,7 @@ pub(super) async fn regenerate_square_hole_visual_asset_for_work( work.cover_image_src = Some( generate_square_hole_image_data_url( state, + request_context, owner_user_id.as_str(), synthetic_session_id.as_str(), profile_id.as_str(), @@ -271,6 +276,7 @@ pub(super) async fn regenerate_square_hole_visual_asset_for_work( work.background_image_src = Some( generate_square_hole_image_data_url( state, + request_context, owner_user_id.as_str(), synthetic_session_id.as_str(), profile_id.as_str(), @@ -301,6 +307,7 @@ pub(super) async fn regenerate_square_hole_visual_asset_for_work( option.image_src = Some( generate_square_hole_image_data_url( state, + request_context, owner_user_id.as_str(), synthetic_session_id.as_str(), profile_id.as_str(), @@ -331,6 +338,7 @@ pub(super) async fn regenerate_square_hole_visual_asset_for_work( option.image_src = Some( generate_square_hole_image_data_url( state, + request_context, owner_user_id.as_str(), synthetic_session_id.as_str(), profile_id.as_str(), @@ -380,6 +388,7 @@ pub(super) async fn regenerate_square_hole_visual_asset_for_work( async fn generate_square_hole_image_data_url( state: &AppState, + request_context: &RequestContext, owner_user_id: &str, session_id: &str, profile_id: &str, @@ -389,10 +398,12 @@ async fn generate_square_hole_image_data_url( size: &str, failure_context: &str, ) -> Result { - let settings = require_openai_image_settings(state)?.with_external_api_audit_context( - Some(owner_user_id.to_string()), - Some(profile_id.to_string()), - ); + let settings = require_openai_image_settings(state)? + .with_external_api_audit_context( + request_context, + Some(owner_user_id.to_string()), + Some(profile_id.to_string()), + ); let http_client = build_openai_image_http_client(&settings)?; let generated = create_openai_image_generation( &http_client, diff --git a/server-rs/crates/api-server/src/state.rs b/server-rs/crates/api-server/src/state.rs index d79393fe..59d47d45 100644 --- a/server-rs/crates/api-server/src/state.rs +++ b/server-rs/crates/api-server/src/state.rs @@ -1042,7 +1042,9 @@ impl fmt::Display for AppStateInitError { match self { Self::Jwt(error) => write!(f, "{error}"), Self::RefreshCookie(error) => write!(f, "{error}"), - Self::AuthStore(error) | Self::DependencyUnavailable(error) | Self::WechatPay(error) => { + Self::AuthStore(error) + | Self::DependencyUnavailable(error) + | Self::WechatPay(error) => { write!(f, "{error}") } Self::SmsProvider(error) => write!(f, "{error}"), diff --git a/server-rs/crates/api-server/src/wooden_fish.rs b/server-rs/crates/api-server/src/wooden_fish.rs index c0a4c216..a181489e 100644 --- a/server-rs/crates/api-server/src/wooden_fish.rs +++ b/server-rs/crates/api-server/src/wooden_fish.rs @@ -526,6 +526,7 @@ async fn maybe_generate_hit_object_asset( let generated = generate_wooden_fish_image_assets( state, + request_context, owner_user_id, session_id, profile_id.as_str(), @@ -659,6 +660,7 @@ struct WoodenFishGeneratedImageAssets { async fn generate_wooden_fish_image_assets( state: &AppState, + request_context: &RequestContext, owner_user_id: &str, session_id: &str, profile_id: &str, @@ -666,6 +668,7 @@ async fn generate_wooden_fish_image_assets( hit_object_reference_image_src: Option<&str>, ) -> Result { let settings = require_openai_image_settings(state)?.with_external_api_audit_context( + request_context, Some(owner_user_id.to_string()), Some(profile_id.to_string()), ); diff --git a/server-rs/crates/api-server/src/work_author.rs b/server-rs/crates/api-server/src/work_author.rs index 2afc2447..c758e3ca 100644 --- a/server-rs/crates/api-server/src/work_author.rs +++ b/server-rs/crates/api-server/src/work_author.rs @@ -98,7 +98,10 @@ pub fn should_rebind_orphan_work_owner( return false; } - !matches!(auth_user_service.get_user_by_id(&owner_user_id), Ok(Some(_))) + !matches!( + auth_user_service.get_user_by_id(&owner_user_id), + Ok(Some(_)) + ) } #[cfg(test)] @@ -137,6 +140,9 @@ mod tests { assert!(should_rebind_orphan_work_owner(&service, "")); assert!(should_rebind_orphan_work_owner(&service, "user_missing")); - assert!(!should_rebind_orphan_work_owner(&service, ORPHAN_WORK_OWNER_USER_ID)); + assert!(!should_rebind_orphan_work_owner( + &service, + ORPHAN_WORK_OWNER_USER_ID + )); } } diff --git a/server-rs/crates/module-auth/src/lib.rs b/server-rs/crates/module-auth/src/lib.rs index 46cbd15b..fa4bb11c 100644 --- a/server-rs/crates/module-auth/src/lib.rs +++ b/server-rs/crates/module-auth/src/lib.rs @@ -807,12 +807,8 @@ impl AuthUserService { display_name: &str, public_user_code: &str, ) -> Result { - self.store.ensure_orphan_work_owner_user( - user_id, - username, - display_name, - public_user_code, - ) + self.store + .ensure_orphan_work_owner_user(user_id, username, display_name, public_user_code) } pub fn get_user_by_id(&self, user_id: &str) -> Result, LogoutError> { @@ -1019,18 +1015,14 @@ impl InMemoryAuthStore { display_name: &str, public_user_code: &str, ) -> Result { - let user_id = normalize_required_string(user_id).ok_or_else(|| { - PasswordEntryError::Store("孤儿作品占位用户 id 不能为空".to_string()) - })?; - let username = normalize_required_string(username).ok_or_else(|| { - PasswordEntryError::Store("孤儿作品占位用户名不能为空".to_string()) - })?; - let display_name = normalize_required_string(display_name).ok_or_else(|| { - PasswordEntryError::Store("孤儿作品占位展示名不能为空".to_string()) - })?; - let public_user_code = normalize_required_string(public_user_code).ok_or_else(|| { - PasswordEntryError::Store("孤儿作品占位陶泥号不能为空".to_string()) - })?; + let user_id = normalize_required_string(user_id) + .ok_or_else(|| PasswordEntryError::Store("孤儿作品占位用户 id 不能为空".to_string()))?; + let username = normalize_required_string(username) + .ok_or_else(|| PasswordEntryError::Store("孤儿作品占位用户名不能为空".to_string()))?; + let display_name = normalize_required_string(display_name) + .ok_or_else(|| PasswordEntryError::Store("孤儿作品占位展示名不能为空".to_string()))?; + let public_user_code = normalize_required_string(public_user_code) + .ok_or_else(|| PasswordEntryError::Store("孤儿作品占位陶泥号不能为空".to_string()))?; let mut state = self .inner diff --git a/server-rs/crates/spacetime-client/src/lib.rs b/server-rs/crates/spacetime-client/src/lib.rs index 22a42518..73e0b55f 100644 --- a/server-rs/crates/spacetime-client/src/lib.rs +++ b/server-rs/crates/spacetime-client/src/lib.rs @@ -6,18 +6,17 @@ mod mapper; mod telemetry; use mapper::*; pub use mapper::{ - AiResultReferenceRecord, AiTaskMutationRecord, AiTaskRecord, AiTaskStageRecord, - AiTextChunkRecord, BarkBattleDraftConfigRecord, BarkBattleRunRecord, + AdminWorkVisibilityRecord, AiResultReferenceRecord, AiTaskMutationRecord, AiTaskRecord, + AiTaskStageRecord, AiTextChunkRecord, BarkBattleDraftConfigRecord, BarkBattleRunRecord, BarkBattleRuntimeConfigRecord, BattleStateRecord, BigFishAgentMessageRecord, BigFishAnchorItemRecord, BigFishAnchorPackRecord, BigFishAssetCoverageRecord, BigFishAssetGenerateRecordInput, BigFishAssetSlotRecord, BigFishBackgroundBlueprintRecord, BigFishDraftCompileRecordInput, BigFishGameDraftRecord, BigFishInputSubmitRecordInput, BigFishLevelBlueprintRecord, BigFishLikeReportRecordInput, BigFishMessageFinalizeRecordInput, BigFishMessageSubmitRecordInput, BigFishPlayReportRecordInput, BigFishRunStartRecordInput, - AdminWorkVisibilityRecord, BigFishRuntimeEntityRecord, BigFishRuntimeParamsRecord, - BigFishRuntimeRunRecord, BigFishSessionCreateRecordInput, BigFishSessionRecord, - BigFishVector2Record, BigFishWorkRemixRecordInput, BigFishWorkSummaryRecord, - CreationEntryConfigRecord, + BigFishRuntimeEntityRecord, BigFishRuntimeParamsRecord, BigFishRuntimeRunRecord, + BigFishSessionCreateRecordInput, BigFishSessionRecord, BigFishVector2Record, + BigFishWorkRemixRecordInput, BigFishWorkSummaryRecord, CreationEntryConfigRecord, CustomWorldAgentActionExecuteRecord, CustomWorldAgentActionExecuteRecordInput, CustomWorldAgentCheckpointRecord, CustomWorldAgentMessageFinalizeRecordInput, CustomWorldAgentMessageRecord, CustomWorldAgentMessageSubmitRecordInput, diff --git a/server-rs/crates/spacetime-client/src/mapper.rs b/server-rs/crates/spacetime-client/src/mapper.rs index 848781f1..fa080b9d 100644 --- a/server-rs/crates/spacetime-client/src/mapper.rs +++ b/server-rs/crates/spacetime-client/src/mapper.rs @@ -115,9 +115,8 @@ pub use self::puzzle::{ PuzzleWorkProfileRecord, PuzzleWorkRemixRecordInput, PuzzleWorkUpsertRecordInput, }; pub use self::runtime::{ - AdminWorkVisibilityRecord, - BigFishGameDraftRecord, BigFishRuntimeEntityRecord, BigFishRuntimeParamsRecord, - BigFishRuntimeRunRecord, CreationEntryConfigRecord, + AdminWorkVisibilityRecord, BigFishGameDraftRecord, BigFishRuntimeEntityRecord, + BigFishRuntimeParamsRecord, BigFishRuntimeRunRecord, CreationEntryConfigRecord, }; pub use self::runtime_profile::{ SquareHoleDropConfirmationRecord, SquareHoleDropFeedbackRecord, SquareHoleRunRecord, diff --git a/server-rs/crates/spacetime-client/src/runtime.rs b/server-rs/crates/spacetime-client/src/runtime.rs index 58ed31fe..624b706d 100644 --- a/server-rs/crates/spacetime-client/src/runtime.rs +++ b/server-rs/crates/spacetime-client/src/runtime.rs @@ -97,14 +97,15 @@ impl SpacetimeClient { .into(); self.call_after_connect("admin_list_work_visibility", move |connection, sender| { - connection - .procedures() - .admin_list_work_visibility_then(procedure_input, move |_, result| { + connection.procedures().admin_list_work_visibility_then( + procedure_input, + move |_, result| { let mapped = result .map_err(SpacetimeClientError::from_sdk_error) .and_then(map_admin_work_visibility_list_procedure_result); send_once(&sender, mapped); - }); + }, + ); }) .await } @@ -126,19 +127,17 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect( - "admin_update_work_visibility", - move |connection, sender| { - connection - .procedures() - .admin_update_work_visibility_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_admin_work_visibility_procedure_result); - send_once(&sender, mapped); - }); - }, - ) + self.call_after_connect("admin_update_work_visibility", move |connection, sender| { + connection.procedures().admin_update_work_visibility_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_admin_work_visibility_procedure_result); + send_once(&sender, mapped); + }, + ); + }) .await } diff --git a/server-rs/crates/spacetime-module/src/auth/procedures.rs b/server-rs/crates/spacetime-module/src/auth/procedures.rs index d4be23ee..8ae86d70 100644 --- a/server-rs/crates/spacetime-module/src/auth/procedures.rs +++ b/server-rs/crates/spacetime-module/src/auth/procedures.rs @@ -66,7 +66,10 @@ fn upsert_auth_snapshot_row( .find(&snapshot_id) .is_some() { - ctx.db.auth_store_snapshot().snapshot_id().delete(&snapshot_id); + ctx.db + .auth_store_snapshot() + .snapshot_id() + .delete(&snapshot_id); } ctx.db.auth_store_snapshot().insert(AuthStoreSnapshot { @@ -106,7 +109,10 @@ fn auth_store_snapshot_wechat_row_id(provider_uid: &str, user_id: &str) -> Strin } fn auth_store_snapshot_union_row_id(union_id: &str, user_id: &str) -> String { - prefixed_snapshot_id(AUTH_STORE_SNAPSHOT_UNION_PREFIX, &format!("{union_id}|{user_id}")) + prefixed_snapshot_id( + AUTH_STORE_SNAPSHOT_UNION_PREFIX, + &format!("{union_id}|{user_id}"), + ) } fn snapshot_has_user_rows(snapshot: &PersistentAuthStoreSnapshot) -> bool { @@ -202,13 +208,7 @@ fn import_auth_store_snapshot_json_value_tx( for stored_user in parsed.users_by_username.into_values() { let user = stored_user.user; let user_id = user.id.clone(); - if ctx - .db - .user_account() - .user_id() - .find(&user_id) - .is_some() - { + if ctx.db.user_account().user_id().find(&user_id).is_some() { ctx.db.user_account().user_id().delete(&user_id); } ctx.db.user_account().insert(UserAccount { @@ -644,10 +644,7 @@ mod tests { PersistentAuthStoreSnapshot { next_user_id: 43, - users_by_username: std::collections::HashMap::from([( - "phone_42".to_string(), - user, - )]), + users_by_username: std::collections::HashMap::from([("phone_42".to_string(), user)]), phone_to_user_id: std::collections::HashMap::from([( "+8613800008000".to_string(), "user_00000042".to_string(), diff --git a/server-rs/crates/spacetime-module/src/custom_world.rs b/server-rs/crates/spacetime-module/src/custom_world.rs index 2c8d2ace..d17c6910 100644 --- a/server-rs/crates/spacetime-module/src/custom_world.rs +++ b/server-rs/crates/spacetime-module/src/custom_world.rs @@ -2603,7 +2603,7 @@ fn is_same_agent_draft_profile_candidate( ) -> bool { row.owner_user_id == owner_user_id && row.deleted_at.is_none() - && row.visible + && row.visible && row.publication_status == CustomWorldPublicationStatus::Draft && row.source_agent_session_id.as_deref() == Some(source_agent_session_id) } diff --git a/server-rs/crates/spacetime-module/src/migration.rs b/server-rs/crates/spacetime-module/src/migration.rs index a3012213..fade23b3 100644 --- a/server-rs/crates/spacetime-module/src/migration.rs +++ b/server-rs/crates/spacetime-module/src/migration.rs @@ -16,7 +16,7 @@ use crate::jump_hop::tables::{ jump_hop_agent_session, jump_hop_event, jump_hop_runtime_run, jump_hop_work_profile, }; use crate::match3d::tables::{ - match3d_agent_message, match3d_agent_session, match3d_runtime_run, match_3_d_work_profile, + match_3_d_work_profile, match3d_agent_message, match3d_agent_session, match3d_runtime_run, }; use crate::puzzle::{ puzzle_agent_message, puzzle_agent_session, puzzle_event, puzzle_leaderboard_entry, diff --git a/server-rs/crates/spacetime-module/src/runtime.rs b/server-rs/crates/spacetime-module/src/runtime.rs index cf85471a..52906c3d 100644 --- a/server-rs/crates/spacetime-module/src/runtime.rs +++ b/server-rs/crates/spacetime-module/src/runtime.rs @@ -1,13 +1,13 @@ -pub mod analytics_date_dimension; mod admin_work_visibility; +pub mod analytics_date_dimension; mod browse_history; pub mod creation_entry_config; mod profile; mod settings; mod snapshots; -pub use analytics_date_dimension::*; pub use admin_work_visibility::*; +pub use analytics_date_dimension::*; pub use browse_history::*; pub use creation_entry_config::*; pub use profile::*; diff --git a/server-rs/crates/spacetime-module/src/runtime/admin_work_visibility.rs b/server-rs/crates/spacetime-module/src/runtime/admin_work_visibility.rs index fd7e3249..13ec22a5 100644 --- a/server-rs/crates/spacetime-module/src/runtime/admin_work_visibility.rs +++ b/server-rs/crates/spacetime-module/src/runtime/admin_work_visibility.rs @@ -1,5 +1,5 @@ -use crate::*; use crate::puzzle::{PuzzleWorkProfileRow, puzzle_work_profile}; +use crate::*; use module_custom_world::CustomWorldPublicationStatus; use module_puzzle::PuzzlePublicationStatus; @@ -93,7 +93,9 @@ fn update_work_visibility_tx( update_wooden_fish_work_visibility(ctx, &profile_id, input.visible) } SOURCE_TYPE_MATCH3D => update_match3d_work_visibility(ctx, &profile_id, input.visible), - SOURCE_TYPE_SQUARE_HOLE => update_square_hole_work_visibility(ctx, &profile_id, input.visible), + SOURCE_TYPE_SQUARE_HOLE => { + update_square_hole_work_visibility(ctx, &profile_id, input.visible) + } SOURCE_TYPE_VISUAL_NOVEL => { update_visual_novel_work_visibility(ctx, &profile_id, input.visible) } @@ -158,7 +160,9 @@ fn puzzle_work_visibility_snapshot(row: &PuzzleWorkProfileRow) -> AdminWorkVisib subtitle: "拼图关卡".to_string(), cover_image_src: row.cover_image_src.clone(), visible: row.visible, - published_at_micros: row.published_at.map(|value| value.to_micros_since_unix_epoch()), + published_at_micros: row + .published_at + .map(|value| value.to_micros_since_unix_epoch()), updated_at_micros: sort_time, } } @@ -234,7 +238,9 @@ fn custom_world_work_visibility_snapshot(row: &CustomWorldProfile) -> AdminWorkV subtitle: row.subtitle.clone(), cover_image_src: row.cover_image_src.clone(), visible: row.visible, - published_at_micros: row.published_at.map(|value| value.to_micros_since_unix_epoch()), + published_at_micros: row + .published_at + .map(|value| value.to_micros_since_unix_epoch()), updated_at_micros: sort_time, } } @@ -287,7 +293,9 @@ fn jump_hop_work_visibility_snapshot(row: &JumpHopWorkProfileRow) -> AdminWorkVi subtitle: "跳一跳".to_string(), cover_image_src: Some(row.cover_image_src.clone()).filter(|value| !value.is_empty()), visible: row.visible, - published_at_micros: row.published_at.map(|value| value.to_micros_since_unix_epoch()), + published_at_micros: row + .published_at + .map(|value| value.to_micros_since_unix_epoch()), updated_at_micros: sort_time, } } @@ -342,7 +350,9 @@ fn wooden_fish_work_visibility_snapshot( subtitle: "敲木鱼".to_string(), cover_image_src: Some(row.cover_image_src.clone()).filter(|value| !value.is_empty()), visible: row.visible, - published_at_micros: row.published_at.map(|value| value.to_micros_since_unix_epoch()), + published_at_micros: row + .published_at + .map(|value| value.to_micros_since_unix_epoch()), updated_at_micros: sort_time, } } @@ -395,7 +405,9 @@ fn match3d_work_visibility_snapshot(row: &Match3DWorkProfileRow) -> AdminWorkVis subtitle: "抓大鹅".to_string(), cover_image_src: Some(row.cover_image_src.clone()).filter(|value| !value.is_empty()), visible: row.visible, - published_at_micros: row.published_at.map(|value| value.to_micros_since_unix_epoch()), + published_at_micros: row + .published_at + .map(|value| value.to_micros_since_unix_epoch()), updated_at_micros: sort_time, } } @@ -450,7 +462,9 @@ fn square_hole_work_visibility_snapshot( subtitle: "方洞挑战".to_string(), cover_image_src: Some(row.cover_image_src.clone()).filter(|value| !value.is_empty()), visible: row.visible, - published_at_micros: row.published_at.map(|value| value.to_micros_since_unix_epoch()), + published_at_micros: row + .published_at + .map(|value| value.to_micros_since_unix_epoch()), updated_at_micros: sort_time, } } @@ -505,7 +519,9 @@ fn visual_novel_work_visibility_snapshot( subtitle: "视觉小说".to_string(), cover_image_src: Some(row.cover_image_src.clone()).filter(|value| !value.is_empty()), visible: row.visible, - published_at_micros: row.published_at.map(|value| value.to_micros_since_unix_epoch()), + published_at_micros: row + .published_at + .map(|value| value.to_micros_since_unix_epoch()), updated_at_micros: sort_time, } } @@ -544,10 +560,10 @@ fn update_big_fish_work_visibility( Ok(snapshot) } -fn big_fish_work_visibility_snapshot( - row: &BigFishCreationSession, -) -> AdminWorkVisibilitySnapshot { - let published_at = row.published_at.map(|value| value.to_micros_since_unix_epoch()); +fn big_fish_work_visibility_snapshot(row: &BigFishCreationSession) -> AdminWorkVisibilitySnapshot { + let published_at = row + .published_at + .map(|value| value.to_micros_since_unix_epoch()); let updated_at = timestamp_sort_micros(row.published_at, row.updated_at); AdminWorkVisibilitySnapshot { source_type: SOURCE_TYPE_BIG_FISH.to_string(), diff --git a/src/components/asset-studio/characterAssetWorkflowPersistence.ts b/src/components/asset-studio/characterAssetWorkflowPersistence.ts index 73cf74dd..deec8414 100644 --- a/src/components/asset-studio/characterAssetWorkflowPersistence.ts +++ b/src/components/asset-studio/characterAssetWorkflowPersistence.ts @@ -3,6 +3,7 @@ import { postApiJson, } from '../../editor/shared/editorApiClient'; import { + appendApiErrorRequestId, fetchJson, parseApiErrorMessage, } from '../../editor/shared/jsonClient'; @@ -265,7 +266,10 @@ export async function putCharacterRoleAssetWorkflow( if (!response.ok) { throw new Error( - parseApiErrorMessage(responseText, '保存角色资产工坊缓存失败'), + appendApiErrorRequestId( + parseApiErrorMessage(responseText, '保存角色资产工坊缓存失败'), + response.headers.get('x-request-id'), + ), ); } diff --git a/src/editor/shared/editorApiClient.ts b/src/editor/shared/editorApiClient.ts index 06f46d17..64359b78 100644 --- a/src/editor/shared/editorApiClient.ts +++ b/src/editor/shared/editorApiClient.ts @@ -1,4 +1,9 @@ -import { fetchJson, parseApiErrorMessage, saveJsonObject } from './jsonClient'; +import { + appendApiErrorRequestId, + fetchJson, + parseApiErrorMessage, + saveJsonObject, +} from './jsonClient'; export const EDITOR_API_BASE_PATH = '/api/editor'; export const ASSETS_API_BASE_PATH = '/api/assets'; @@ -69,7 +74,7 @@ export async function postApiJson( const responseText = await response.text(); if (!response.ok) { - throw new Error(parseApiErrorMessage(responseText, fallbackMessage)); + throw new Error(appendApiErrorRequestId(parseApiErrorMessage(responseText, fallbackMessage), response.headers.get('x-request-id'))); } return responseText ? (JSON.parse(responseText) as T) : ({} as T); diff --git a/src/editor/shared/jsonClient.ts b/src/editor/shared/jsonClient.ts index 1b279350..107afadd 100644 --- a/src/editor/shared/jsonClient.ts +++ b/src/editor/shared/jsonClient.ts @@ -1,6 +1,7 @@ import { API_RESPONSE_ENVELOPE_HEADER, API_RESPONSE_ENVELOPE_VERSION, + appendApiErrorRequestId, parseApiErrorMessage, unwrapApiResponse, } from '../../../packages/shared/src/http'; @@ -17,7 +18,15 @@ export async function fetchJson( const responseText = await response.text(); if (!response.ok) { - throw new Error(parseApiErrorMessage(responseText, `${fallbackMessage}: ${response.status}`)); + throw new Error( + appendApiErrorRequestId( + parseApiErrorMessage( + responseText, + `${fallbackMessage}: ${response.status}`, + ), + response.headers.get('x-request-id'), + ), + ); } return responseText @@ -41,8 +50,13 @@ export async function saveJsonObject( const responseText = await response.text(); if (!response.ok) { - throw new Error(parseApiErrorMessage(responseText, fallbackMessage)); + throw new Error( + appendApiErrorRequestId( + parseApiErrorMessage(responseText, fallbackMessage), + response.headers.get('x-request-id'), + ), + ); } } -export { parseApiErrorMessage }; +export { appendApiErrorRequestId, parseApiErrorMessage }; diff --git a/src/services/aiService.ts b/src/services/aiService.ts index 277444b8..eda0fb45 100644 --- a/src/services/aiService.ts +++ b/src/services/aiService.ts @@ -14,7 +14,10 @@ import type { GenerateCustomWorldProfileInput, GenerateCustomWorldProfileOptions, } from '../../packages/shared/src/contracts/runtime'; -import { parseApiErrorMessage } from '../../packages/shared/src/http'; +import { + appendApiErrorRequestId, + parseApiErrorMessage, +} from '../../packages/shared/src/http'; import type { AIResponse, Character, @@ -93,7 +96,12 @@ async function requestPlainTextStream( if (!response.ok) { const responseText = await response.text(); - throw new Error(parseApiErrorMessage(responseText, '流式请求失败')); + throw new Error( + appendApiErrorRequestId( + parseApiErrorMessage(responseText, '流式请求失败'), + response.headers.get('x-request-id'), + ), + ); } if (!response.body) { @@ -488,7 +496,12 @@ export async function streamNpcChatTurn( if (!response.ok) { const responseText = await response.text(); - throw new Error(parseApiErrorMessage(responseText, 'NPC 聊天续写失败')); + throw new Error( + appendApiErrorRequestId( + parseApiErrorMessage(responseText, 'NPC 聊天续写失败'), + response.headers.get('x-request-id'), + ), + ); } if (!response.body) { diff --git a/src/services/apiClient.test.ts b/src/services/apiClient.test.ts index f969959d..51d11e0e 100644 --- a/src/services/apiClient.test.ts +++ b/src/services/apiClient.test.ts @@ -547,6 +547,7 @@ describe('apiClient', () => { routeVersion: 'runtime.v2', }, }); + expect((capturedError as Error).message).toContain('requestId: req-body'); }); it('uses api error details.message as ApiClientError message', async () => { diff --git a/src/services/apiClient.ts b/src/services/apiClient.ts index 0825dc17..a3eecde0 100644 --- a/src/services/apiClient.ts +++ b/src/services/apiClient.ts @@ -671,9 +671,14 @@ async function buildApiClientError( ) { const responseText = await response.text(); const parsedError = parseApiErrorShape(responseText); + const requestId = + parsedError?.meta.requestId ?? + response.headers.get(REQUEST_ID_HEADER) ?? + undefined; + const baseMessage = parseApiErrorMessage(responseText, fallbackMessage); return new ApiClientError({ - message: parseApiErrorMessage(responseText, fallbackMessage), + message: requestId ? `${baseMessage}(requestId: ${requestId})` : baseMessage, status: response.status, code: parsedError?.code ?? `HTTP_${response.status || 0}`, details: parsedError?.details ?? null, @@ -682,10 +687,7 @@ async function buildApiClientError( parsedError?.meta.apiVersion ?? response.headers.get(API_VERSION_HEADER) ?? API_VERSION, - requestId: - parsedError?.meta.requestId ?? - response.headers.get(REQUEST_ID_HEADER) ?? - undefined, + requestId, routeVersion: parsedError?.meta.routeVersion ?? response.headers.get(ROUTE_VERSION_HEADER) ?? diff --git a/src/services/assetReadUrlService.ts b/src/services/assetReadUrlService.ts index 4a6e57a3..49812b89 100644 --- a/src/services/assetReadUrlService.ts +++ b/src/services/assetReadUrlService.ts @@ -1,4 +1,7 @@ -import { parseApiErrorMessage } from '../../packages/shared/src/http'; +import { + appendApiErrorRequestId, + parseApiErrorMessage, +} from '../../packages/shared/src/http'; import { ApiClientError, BACKGROUND_AUTH_REQUEST_OPTIONS, @@ -338,7 +341,12 @@ export async function readAssetBytes( if (!response.ok) { const message = await response .text() - .then((text) => parseApiErrorMessage(text, '读取资源内容失败')) + .then((text) => + appendApiErrorRequestId( + parseApiErrorMessage(text, '读取资源内容失败'), + response.headers.get('x-request-id'), + ), + ) .catch(() => ''); throw new Error(message || '读取资源内容失败'); } diff --git a/src/services/creation-agent/creationAgentClientFactory.ts b/src/services/creation-agent/creationAgentClientFactory.ts index dacbbbc7..a7fd288a 100644 --- a/src/services/creation-agent/creationAgentClientFactory.ts +++ b/src/services/creation-agent/creationAgentClientFactory.ts @@ -1,4 +1,4 @@ -import { parseApiErrorMessage } from '../../../packages/shared/src/http'; +import { appendApiErrorRequestId, parseApiErrorMessage } from '../../../packages/shared/src/http'; import type { TextStreamOptions } from '../aiTypes'; import { type ApiRetryOptions, @@ -64,7 +64,7 @@ async function openCreationAgentSsePost( if (!response.ok) { const responseText = await response.text(); - throw new Error(parseApiErrorMessage(responseText, fallbackMessage)); + throw new Error(appendApiErrorRequestId(parseApiErrorMessage(responseText, fallbackMessage), response.headers.get('x-request-id'))); } if (!response.body) { diff --git a/src/services/creative-agent/creativeAgentClient.ts b/src/services/creative-agent/creativeAgentClient.ts index 60c980f6..fc279f7b 100644 --- a/src/services/creative-agent/creativeAgentClient.ts +++ b/src/services/creative-agent/creativeAgentClient.ts @@ -7,7 +7,7 @@ import type { CreativeDraftEditStreamRequest, StreamCreativeAgentMessageRequest, } from '../../../packages/shared/src/contracts/creativeAgent'; -import { parseApiErrorMessage } from '../../../packages/shared/src/http'; +import { appendApiErrorRequestId, parseApiErrorMessage } from '../../../packages/shared/src/http'; import type { TextStreamOptions } from '../aiTypes'; import { fetchWithApiAuth, requestJson } from '../apiClient'; import { @@ -42,7 +42,7 @@ async function openCreativeAgentSsePost( if (!response.ok) { const responseText = await response.text(); - throw new Error(parseApiErrorMessage(responseText, fallbackMessage)); + throw new Error(appendApiErrorRequestId(parseApiErrorMessage(responseText, fallbackMessage), response.headers.get('x-request-id'))); } if (!response.body) { diff --git a/src/services/rpg-creation/rpgCreationRequestHelpers.ts b/src/services/rpg-creation/rpgCreationRequestHelpers.ts index 7e3624f0..2ee363cd 100644 --- a/src/services/rpg-creation/rpgCreationRequestHelpers.ts +++ b/src/services/rpg-creation/rpgCreationRequestHelpers.ts @@ -1,4 +1,4 @@ -import { parseApiErrorMessage } from '../../../packages/shared/src/http'; +import { appendApiErrorRequestId, parseApiErrorMessage } from '../../../packages/shared/src/http'; import { fetchWithApiAuth, requestJson } from '../apiClient'; export async function requestRpgCreationPostJson( @@ -32,7 +32,7 @@ export async function openRpgCreationSsePost( if (!response.ok) { const responseText = await response.text(); - throw new Error(parseApiErrorMessage(responseText, fallbackMessage)); + throw new Error(appendApiErrorRequestId(parseApiErrorMessage(responseText, fallbackMessage), response.headers.get('x-request-id'))); } if (!response.body) { diff --git a/src/services/visual-novel-runtime/visualNovelRuntimeClient.ts b/src/services/visual-novel-runtime/visualNovelRuntimeClient.ts index b2210823..8f29ffe4 100644 --- a/src/services/visual-novel-runtime/visualNovelRuntimeClient.ts +++ b/src/services/visual-novel-runtime/visualNovelRuntimeClient.ts @@ -16,7 +16,7 @@ import type { VisualNovelStartRunRequest, VisualNovelWorksResponse, } from '../../../packages/shared/src/contracts/visualNovel'; -import { parseApiErrorMessage } from '../../../packages/shared/src/http'; +import { appendApiErrorRequestId, parseApiErrorMessage } from '../../../packages/shared/src/http'; import type { TextStreamOptions } from '../aiTypes'; import { type ApiRetryOptions, @@ -100,7 +100,7 @@ async function openVisualNovelRuntimeSsePost( if (!response.ok) { const responseText = await response.text(); - throw new Error(parseApiErrorMessage(responseText, fallbackMessage)); + throw new Error(appendApiErrorRequestId(parseApiErrorMessage(responseText, fallbackMessage), response.headers.get('x-request-id'))); } if (!response.body) {