From c7da67ed47f7b520c41078abca23e9fde3de99e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 1 Oct 2026 16:55:29 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9D=9E=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=9B=9E?= =?UTF-8?q?=E6=98=BE=E9=99=90=E9=95=BF=E8=A1=A5=E9=BD=90=E5=88=B0=E5=86=85?= =?UTF-8?q?=E8=81=94=E5=88=A4=E7=B1=BB=E5=9E=8B=E7=9A=84=E5=9B=9B=E5=A4=84?= =?UTF-8?q?=EF=BC=88=E6=89=BF=E6=8E=A5=E4=B8=8A=E4=B8=80=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - arguments.rs 导出 not_text_repr,optional_text 与内联判类型的字段共用同一份截断口径 - 抠图 backgroundMode / screenColor、美术准备 mode、生成图片 kind 这四处原来直接 value.to_string(),现在同样限长到 120 字符 - 测试补一条:art_preparation_mode 的超大数组回显必须带截断标记且长度可控 验证:cargo test --bin genarrative-ai-game-creator-shell -- non_text_field_values 通过;cargo fmt --check 通过 --- .../src-tauri/src/agent/direct_tool_bridge.rs | 26 +++++++++++++++---- .../src-tauri/src/agent/tool/arguments.rs | 13 +++++++--- 2 files changed, 30 insertions(+), 9 deletions(-) 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 254ebd93e..d1d39fac5 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 @@ -1,6 +1,8 @@ use super::*; use crate::agent::tool::apply_patch::error::ApplyPatchError; -use crate::agent::tool::arguments::{optional_text, page, required_text, ToolPage, ToolTextField}; +use crate::agent::tool::arguments::{ + not_text_repr, optional_text, page, required_text, ToolPage, ToolTextField, +}; use crate::agent::tool::browser_playtest::error::BrowserPlaytestError; #[cfg(all(windows, feature = "cocos-editor-execute"))] use crate::agent::tool::cocos_execute::error::CocosExecuteError; @@ -2398,7 +2400,7 @@ pub(in crate::agent) fn remove_background_input( value .as_str() .ok_or_else(|| RemoveBackgroundError::BackgroundModeNotText { - got: value.to_string(), + got: not_text_repr(value), })?; if !matches!(mode, "complex" | "flat") { return Err(RemoveBackgroundError::BackgroundModeUnsupported { @@ -2418,7 +2420,7 @@ pub(in crate::agent) fn remove_background_input( value .as_str() .ok_or_else(|| RemoveBackgroundError::ScreenColorNotText { - got: value.to_string(), + got: not_text_repr(value), })?; let valid_hex = color.len() == 7 && color.starts_with('#') @@ -2733,7 +2735,7 @@ fn art_preparation_mode( Some(Value::String(value)) => DirectTaonierArtPreparationMode::from_tool_value(Some(value)) .map_err(|_| PrepareGameArtError::ModeUnsupported { got: value.clone() }), Some(value) => Err(PrepareGameArtError::ModeNotText { - got: value.to_string(), + got: not_text_repr(value), }), } } @@ -2914,7 +2916,7 @@ fn bridge_image_generation_kind( Some(Value::String(value)) => value.trim(), Some(value) => { return Err(GenerateImageError::KindNotText { - got: value.to_string(), + got: not_text_repr(value), }) } }; @@ -4988,6 +4990,20 @@ mod tests { oversized_not_text.chars().count() ); + let inline_not_text = art_preparation_mode(&json!({ + "mode": vec!["需要回显但必须截断的模式值"; 40] + })) + .expect_err("mode 不是字符串时必须被拒绝") + .to_user_msg(); + assert!( + inline_not_text.contains("mode 必须是字符串"), + "{inline_not_text}" + ); + assert!( + inline_not_text.contains('…') && inline_not_text.chars().count() < 200, + "内联判类型的字段回显也要限长:{inline_not_text}" + ); + let brief_not_text = required_text(&json!({ "brief": 5 }), PrepareGameArtTextField::Brief) .expect_err("brief 不是字符串时必须被拒绝") .to_user_msg(); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/tool/arguments.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/tool/arguments.rs index 8b49b1f30..fd55ead6d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/tool/arguments.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/tool/arguments.rs @@ -14,6 +14,14 @@ use crate::agent::runtime_state::truncate_agent_runtime_text; /// 诊断 sidecar;`got` 会同时出现在 `to_user_msg` 和序列化后的 `error` 里。 const NOT_TEXT_GOT_MAX_CHARS: usize = 120; +/// 把「本该是字符串、实际给了别的类型」的值压成有界回显,供各工具的 `*NotText` 变体拼文案。 +/// +/// 走共享 [`ToolTextField::not_text`] 的字段自动走这里;像抠图 `backgroundMode`、美术准备 +/// `mode`、生成图片 `kind` 这类在解析里内联判类型的字段,也要用同一份口径。 +pub(crate) fn not_text_repr(value: &Value) -> String { + truncate_agent_runtime_text(&value.to_string(), NOT_TEXT_GOT_MAX_CHARS) +} + /// 一个会被边界校验的文本字段。 pub(crate) trait ToolTextField: Copy { fn key(self) -> &'static str; @@ -42,10 +50,7 @@ pub(crate) fn optional_text, E>( return Ok(None); }; let Some(value) = value.as_str() else { - return Err(field.not_text(truncate_agent_runtime_text( - &value.to_string(), - NOT_TEXT_GOT_MAX_CHARS, - ))); + return Err(field.not_text(not_text_repr(value))); }; let value = value.trim(); if value.is_empty() {