统一画布生成 kind 为共享枚举

工具白名单与 schema 改用 GameCreationAppAssetKind
canonical 输入在生成适配边界映射旧账本值
This commit is contained in:
2026-09-17 22:52:38 +08:00
parent 8dce016552
commit 3d4c7929c3
6 changed files with 70 additions and 17 deletions
@@ -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"),
}
}
@@ -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::<Vec<_>>()
.join("|");
let limited_command_contract = if runtime_owner_artifact_validation_available {
"当前固定 owner 不得调用 command.run_limited 或 project.verify;完成固定正式产物后直接交付,由 Runtime 在收束门内执行结构验证。".to_string()
} else {
@@ -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,
]
);
}
}
@@ -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"));
}
@@ -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::<Vec<_>>();
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::<Vec<_>>();
expected.push(Value::Null);
@@ -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 {