生成图片的允许值与边界提成常量,校验与诊断文案共用同一份

- 新增 GENERATE_IMAGE_ASPECT_RATIOS / _IMAGE_SIZES / _SLICE_MODES 与 grid 轴、sliceCount 的 MIN/MAX 常量,放在 direct_tool_bridge 的 GENERATE_IMAGE_ARGUMENTS 旁
- generate_image_input 的 aspectRatio / imageSize / sliceMode / grid 轴区间 / sliceCount 区间校验改为读常量;generate_image/error.rs 的对应文案也改为读同一批常量(KindUnsupported 早已从 PLATFORM_ART_ASSET_GENERATION_KINDS 派生,这次把其余几组对齐)
- 只改取值来源,判定范围与文案取值不变
- 未纳入本次:commands.rs 的 LOCAL_PROJECT_ASSET_ASPECT_RATIOS/_IMAGE_SIZES 与 agent_native_tools.rs 的 canvas.asset_generate schema 仍是各自的副本(面向非 agent 的栏目画布入口),统一它们属于另一次改动

验证:cargo test --bin genarrative-ai-game-creator-shell -- agent::direct_tool_bridge:: agent::direct_tools_mcp:: 71 passed
This commit is contained in:
2026-10-01 14:35:53 +08:00
parent 235adde72c
commit 0d21f838ed
2 changed files with 38 additions and 13 deletions
@@ -2711,6 +2711,17 @@ async fn bridge_prepare_game_art(
Ok(result)
}
/// `agc_generate_image` 的受支持取值与边界:解析校验与失败文案共用同一份,
/// 避免两边各写一遍、改动时静默漂移。
pub(in crate::agent) const GENERATE_IMAGE_ASPECT_RATIOS: &[&str] =
&["1:1", "2:3", "3:2", "9:16", "16:9"];
pub(in crate::agent) const GENERATE_IMAGE_IMAGE_SIZES: &[&str] = &["0.5K", "1K", "2K"];
pub(in crate::agent) const GENERATE_IMAGE_SLICE_MODES: &[&str] = &["connected-components", "grid"];
pub(in crate::agent) const GENERATE_IMAGE_GRID_AXIS_MIN: u32 = 1;
pub(in crate::agent) const GENERATE_IMAGE_GRID_AXIS_MAX: u32 = 32;
pub(in crate::agent) const GENERATE_IMAGE_SLICE_COUNT_MIN: u64 = 1;
pub(in crate::agent) const GENERATE_IMAGE_SLICE_COUNT_MAX: u64 = 256;
/// `agc_generate_image` 允许的入参字段。
const GENERATE_IMAGE_ARGUMENTS: &[&str] = &[
"prompt",
@@ -2962,15 +2973,12 @@ pub(in crate::agent) fn generate_image_input(
let aspect_ratio =
generate_image_optional_text(arguments, GenerateImageTextField::AspectRatio)?
.unwrap_or_else(|| "1:1".to_string());
if !matches!(
aspect_ratio.as_str(),
"1:1" | "2:3" | "3:2" | "9:16" | "16:9"
) {
if !GENERATE_IMAGE_ASPECT_RATIOS.contains(&aspect_ratio.as_str()) {
return Err(GenerateImageError::AspectRatioUnsupported { got: aspect_ratio });
}
let image_size = generate_image_optional_text(arguments, GenerateImageTextField::ImageSize)?
.unwrap_or_else(|| "1K".to_string());
if !matches!(image_size.as_str(), "0.5K" | "1K" | "2K") {
if !GENERATE_IMAGE_IMAGE_SIZES.contains(&image_size.as_str()) {
return Err(GenerateImageError::ImageSizeUnsupported { got: image_size });
}
let asset_name = generate_image_optional_text(arguments, GenerateImageTextField::AssetName)?
@@ -2979,7 +2987,7 @@ pub(in crate::agent) fn generate_image_input(
let slice_mode = generate_image_optional_text(arguments, GenerateImageTextField::SliceMode)?;
if slice_mode
.as_deref()
.is_some_and(|mode| !matches!(mode, "connected-components" | "grid"))
.is_some_and(|mode| !GENERATE_IMAGE_SLICE_MODES.contains(&mode))
{
return Err(GenerateImageError::SliceModeUnsupported {
got: slice_mode.unwrap_or_default(),
@@ -2991,7 +2999,9 @@ pub(in crate::agent) fn generate_image_input(
return Err(GenerateImageError::GridAxesMissing);
}
for (axis, value) in [("gridX", grid_x), ("gridY", grid_y)] {
if let Some(got) = value.filter(|value| !(1..=32).contains(value)) {
if let Some(got) = value.filter(|value| {
!(GENERATE_IMAGE_GRID_AXIS_MIN..=GENERATE_IMAGE_GRID_AXIS_MAX).contains(value)
}) {
return Err(GenerateImageError::GridOutOfRange { axis, got });
}
}
@@ -3001,7 +3011,10 @@ pub(in crate::agent) fn generate_image_input(
.map(|value| {
value
.as_u64()
.filter(|count| (1..=256).contains(count))
.filter(|count| {
(GENERATE_IMAGE_SLICE_COUNT_MIN..=GENERATE_IMAGE_SLICE_COUNT_MAX)
.contains(count)
})
.map(|count| count as usize)
.ok_or_else(|| GenerateImageError::SliceCountInvalid {
got: value.to_string(),
@@ -2,6 +2,11 @@
//!
//! 这里只放错误本身;参数解析与工具执行留在 `direct_tool_bridge`。
use crate::agent::direct_tool_bridge::{
GENERATE_IMAGE_ASPECT_RATIOS, GENERATE_IMAGE_GRID_AXIS_MAX, GENERATE_IMAGE_GRID_AXIS_MIN,
GENERATE_IMAGE_IMAGE_SIZES, GENERATE_IMAGE_SLICE_COUNT_MAX, GENERATE_IMAGE_SLICE_COUNT_MIN,
GENERATE_IMAGE_SLICE_MODES,
};
use crate::agent::generation::PLATFORM_ART_ASSET_GENERATION_KINDS;
use crate::agent::tool::error::{ProjectPermissionRejection, ToolArgumentsRejection, ToolFailure};
use shared_contracts::game_creation_app::GameCreationAppAssetKind;
@@ -84,7 +89,8 @@ impl ToolFailure for GenerateImageError {
"生成图片失败:aspectRatio 含有控制字符。".to_string()
}
Self::AspectRatioUnsupported { got } => format!(
"生成图片失败:aspectRatio「{got}」不是受支持的比例,只能是 1:1、2:3、3:2、9:16、16:9。"
"生成图片失败:aspectRatio「{got}」不是受支持的比例,只能是 {}。",
GENERATE_IMAGE_ASPECT_RATIOS.join("、")
),
Self::ImageSizeEmpty => "生成图片失败:imageSize 传了空值。".to_string(),
Self::ImageSizeTooLong {
@@ -95,7 +101,8 @@ impl ToolFailure for GenerateImageError {
"生成图片失败:imageSize 含有控制字符。".to_string()
}
Self::ImageSizeUnsupported { got } => format!(
"生成图片失败:imageSize「{got}」不是受支持的图片尺寸,只能是 0.5K、1K、2K。"
"生成图片失败:imageSize「{got}」不是受支持的图片尺寸,只能是 {}。",
GENERATE_IMAGE_IMAGE_SIZES.join("、")
),
Self::AssetNameEmpty => "生成图片失败:assetName 传了空值。".to_string(),
Self::AssetNameTooLong {
@@ -122,7 +129,8 @@ impl ToolFailure for GenerateImageError {
"生成图片失败:sliceMode 含有控制字符。".to_string()
}
Self::SliceModeUnsupported { got } => format!(
"生成图片失败:sliceMode「{got}」不受支持,只允许 connected-components 或 grid。"
"生成图片失败:sliceMode「{got}」不受支持,只允许 {}。",
GENERATE_IMAGE_SLICE_MODES.join("、")
),
Self::GridAxisNotInteger { axis } => {
format!("生成图片失败:{axis} 必须是整数。")
@@ -131,10 +139,14 @@ impl ToolFailure for GenerateImageError {
"生成图片失败:sliceMode=grid 必须同时提供 gridX 与 gridY。".to_string()
}
Self::GridOutOfRange { axis, got } => {
format!("生成图片失败:{axis}={got} 超出范围,gridX/gridY 必须在 1 到 32 之间。")
format!(
"生成图片失败:{axis}={got} 超出范围,gridX/gridY 必须在 {GENERATE_IMAGE_GRID_AXIS_MIN} 到 {GENERATE_IMAGE_GRID_AXIS_MAX} 之间。"
)
}
Self::SliceCountInvalid { got } => {
format!("生成图片失败:sliceCount「{got}」非法,必须是 1 到 256 的整数。")
format!(
"生成图片失败:sliceCount「{got}」非法,必须是 {GENERATE_IMAGE_SLICE_COUNT_MIN} 到 {GENERATE_IMAGE_SLICE_COUNT_MAX} 的整数。"
)
}
Self::SpritesheetSliceModeMissing => format!(
"生成图片失败:kind={} 必须显式声明 sliceMode,没有默认值:需求要求等分网格、固定槽位或指定行列数时传 sliceMode=grid 并提供 gridX/gridY;自由排布、数量不定或只要求一张图集时传 sliceMode=connected-components。",