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 4e86ef224..3ede10478 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 @@ -473,12 +473,11 @@ pub(crate) fn normalize_platform_art_asset_generation_kind(kind: &str) -> Option }) } -/// 平台美术生成请求的 `assetKind` → manifest 资源 kind。 +/// 把平台生成适配层收到的 `asset_kind` 映射为 manifest 资产的正式 kind。 /// -/// `PlatformArtAssetGenerationOptions::asset_kind` 是**平台请求词汇**(`ui-prototype` / -/// `art-spritesheet` / `game-background` / `game-art` …),跟 manifest 的正式 -/// `GameCreationAppAssetKind` 不是同一套词表。生成结果登记进 manifest 之前必须在这里显式 -/// 转换,否则 manifest 会落进非 canonical 值,读侧只能靠 shared-contracts 的别名表兜底。 +/// 生成工具的新输入由 `GameCreationAppAssetKind::CANVAS_ASSET_KINDS` 约束;这里保留对 +/// 平台请求值和旧生成账本值的显式映射,只在登记边界转换为 manifest canonical kind, +/// 避免把平台字段直接写入 `assets[].kind`。 /// /// 词表外的值不猜:返回 `Unknown`,由调用方按既有未知输入口径处理。 pub(crate) fn platform_art_asset_manifest_kind(asset_kind: &str) -> GameCreationAppAssetKind { @@ -490,9 +489,8 @@ pub(crate) fn platform_art_asset_manifest_kind(asset_kind: &str) -> GameCreation "art-spritesheet" | "icon-spritesheet" => GameCreationAppAssetKind::IconSpritesheet, "publication-material" => GameCreationAppAssetKind::PublicationMaterial, "game-background" | "scene" => GameCreationAppAssetKind::Scene, - // 画板/平台词汇表(`AGENT_RUNTIME_CANVAS_ASSET_KINDS`)到 manifest kind 的**唯一** - // 映射点:认不出的原值收口成 `Unknown` 并留痕(回调接到壳层 `app_log!`), - // manifest 侧不再有第二张别名表。 + // 其它平台适配输入也只在这里映射到 manifest kind;认不出的原值收口成 `Unknown` + // 并留痕(回调接到壳层 `app_log!`),manifest 侧不再维护第二张别名表。 _ => GameCreationAppAssetKind::parse_with_context(asset_kind, "canvas.asset_kind"), } } diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs index b38ed1023..f30084bfd 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs @@ -336,7 +336,11 @@ fn build_game_creator_agent_background_tool_plan_request_at( render_game_creator_agent_runtime_steers_for_prompt(root, agent_id, session_id, run_id)?; let loop_index = loop_index.saturating_add(1); let context_preload_notice = game_creator_agent_context_preload_notice(agent_id); - let canvas_asset_kind_catalog = AGENT_RUNTIME_CANVAS_ASSET_KINDS.join("|"); + let canvas_asset_kind_catalog = AGENT_RUNTIME_CANVAS_ASSET_KINDS + .iter() + .map(|kind| kind.as_str()) + .collect::>() + .join("|"); let limited_command_contract = if runtime_owner_artifact_validation_available { "当前固定 owner 不得调用 command.run_limited 或 project.verify;完成固定正式产物后直接交付,由 Runtime 在收束门内执行结构验证。".to_string() } else { diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_policy_snapshot.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_policy_snapshot.rs index 68e315d3d..7c688b2b9 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_policy_snapshot.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_policy_snapshot.rs @@ -1,7 +1,7 @@ use super::*; -pub(crate) const AGENT_RUNTIME_CANVAS_ASSET_KINDS: &[&str] = - &["game-art", "icon-spec", "ui-prototype", "art-spritesheet"]; +pub(crate) const AGENT_RUNTIME_CANVAS_ASSET_KINDS: &[GameCreationAppAssetKind] = + GameCreationAppAssetKind::CANVAS_ASSET_KINDS; #[cfg(test)] mod canvas_asset_kind_contract_tests { @@ -11,7 +11,12 @@ mod canvas_asset_kind_contract_tests { fn canvas_asset_kind_catalog_preserves_authoritative_contract() { assert_eq!( AGENT_RUNTIME_CANVAS_ASSET_KINDS, - &["game-art", "icon-spec", "ui-prototype", "art-spritesheet"] + &[ + GameCreationAppAssetKind::Image, + GameCreationAppAssetKind::IconSpec, + GameCreationAppAssetKind::UiDesign, + GameCreationAppAssetKind::IconSpritesheet, + ] ); } } diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/media.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/media.rs index 6f7aa3441..92fb37f84 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/media.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/media.rs @@ -16,7 +16,26 @@ pub(crate) const AGENT_RUNTIME_ART_SPEC_PATH: &str = "assets/art-spec.png"; pub(crate) const AGENT_RUNTIME_ART_SPRITESHEET_PATH: &str = "assets/art-spritesheet.png"; fn agent_runtime_canvas_asset_kind_is_supported(asset_kind: &str) -> bool { - AGENT_RUNTIME_CANVAS_ASSET_KINDS.contains(&asset_kind) + let parsed = match asset_kind { + // 仅用于恢复旧的生成账本;新工具 schema 只暴露 canonical enum 值。 + "game-art" => GameCreationAppAssetKind::Image, + "ui-prototype" => GameCreationAppAssetKind::UiDesign, + "art-spritesheet" => GameCreationAppAssetKind::IconSpritesheet, + _ => GameCreationAppAssetKind::parse_with_context(asset_kind, "canvas.asset_generate"), + }; + AGENT_RUNTIME_CANVAS_ASSET_KINDS.contains(&parsed) +} + +fn canvas_asset_kind_generation_value(asset_kind: &str) -> Option<&'static str> { + match asset_kind.trim() { + // 新工具输入使用 GameCreationAppAssetKind 的 canonical 值;生成适配器内部仍 + // 使用平台请求所需的固定值,直到请求边界完成迁移。 + "image" | "game-art" => Some("game-art"), + "icon-spec" => Some("icon-spec"), + "ui-design" | "ui-prototype" => Some("ui-prototype"), + "icon-spritesheet" | "art-spritesheet" => Some("art-spritesheet"), + _ => None, + } } pub(in crate::agent) fn design_foundation_ui_page_output_path_is_valid(path: &str) -> bool { @@ -543,7 +562,10 @@ pub(in crate::agent) async fn observe_agent_runtime_platform_art_asset_generatio let output_path = agent_runtime_tool_input_text(input, &["outputPath", "output_path"]); let aspect_ratio = agent_runtime_tool_input_text(input, &["aspectRatio", "aspect_ratio"]); let image_size = agent_runtime_tool_input_text(input, &["imageSize", "image_size"]); - let asset_kind = agent_runtime_tool_input_text(input, &["assetKind", "asset_kind"]); + let requested_asset_kind = agent_runtime_tool_input_text(input, &["assetKind", "asset_kind"]); + let asset_kind = canvas_asset_kind_generation_value(&requested_asset_kind) + .map(str::to_string) + .unwrap_or(requested_asset_kind); let asset_label = agent_runtime_tool_input_text(input, &["assetLabel", "asset_label"]); let replace_existing = input .get("replaceExisting") @@ -1173,8 +1195,21 @@ mod platform_art_generation_observation_tests { #[test] fn canvas_asset_kind_validation_accepts_shared_catalog() { for asset_kind in AGENT_RUNTIME_CANVAS_ASSET_KINDS { - assert!(agent_runtime_canvas_asset_kind_is_supported(asset_kind)); + assert!(agent_runtime_canvas_asset_kind_is_supported( + asset_kind.as_str() + )); } + for legacy_kind in ["game-art", "ui-prototype", "art-spritesheet"] { + assert!(agent_runtime_canvas_asset_kind_is_supported(legacy_kind)); + } + assert_eq!( + canvas_asset_kind_generation_value("image"), + Some("game-art") + ); + assert_eq!( + canvas_asset_kind_generation_value("ui-design"), + Some("ui-prototype") + ); assert!(!agent_runtime_canvas_asset_kind_is_supported("unsupported")); } diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs b/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs index 45ba5c505..6ef2ec388 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs @@ -1306,7 +1306,7 @@ fn runtime_tool_input_schema(tool: &str) -> Value { "canvas.asset_generate" => { let mut asset_kinds = AGENT_RUNTIME_CANVAS_ASSET_KINDS .iter() - .map(|kind| Value::String((*kind).to_string())) + .map(|kind| Value::String(kind.as_str().to_string())) .collect::>(); asset_kinds.push(Value::Null); json!({ @@ -1867,7 +1867,7 @@ mod tests { let schema = runtime_tool_input_schema("canvas.asset_generate"); let mut expected = AGENT_RUNTIME_CANVAS_ASSET_KINDS .iter() - .map(|kind| Value::String((*kind).to_string())) + .map(|kind| Value::String(kind.as_str().to_string())) .collect::>(); expected.push(Value::Null); diff --git a/server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs b/server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs index 7e41d7872..91ca76794 100644 --- a/server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs +++ b/server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs @@ -70,6 +70,17 @@ macro_rules! game_creation_app_asset_kinds { /// 全部变体(末尾是 `Unknown`),按声明顺序。 pub const ALL: &'static [Self] = &[$(Self::$variant,)+]; + /// `canvas.asset_generate` 当前允许提交的资源 kind 子集。 + /// + /// 工具输入与 manifest 持久化共用同一组 canonical 枚举值,不再维护第二份 + /// 字符串白名单。工具 schema、prompt 和执行前校验都应从这里派生。 + pub const CANVAS_ASSET_KINDS: &'static [Self] = &[ + Self::Image, + Self::IconSpec, + Self::UiDesign, + Self::IconSpritesheet, + ]; + /// 线上值:kind 字符串表只有这一份,就是上面那张声明表。 pub const fn as_str(self) -> &'static str { match self {