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 bfff40a20..254ebd93e 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 @@ -4965,6 +4965,29 @@ mod tests { ); assert!(!prompt_not_text.contains("不能为空"), "{prompt_not_text}"); + let oversized_not_text = match resource_generation_input(&json!({ + "kind": "video", + "mode": "create", + "prompt": vec!["需要回显但必须截断的字段内容"; 40], + "assetName": "开场" + })) { + Ok(_) => panic!("prompt 不是字符串时必须被拒绝"), + Err(error) => error.to_user_msg(), + }; + assert!( + oversized_not_text.contains("prompt 必须是字符串"), + "{oversized_not_text}" + ); + assert!( + oversized_not_text.contains('…'), + "超长回显必须带截断标记:{oversized_not_text}" + ); + assert!( + oversized_not_text.chars().count() < 200, + "超长回显必须被截断:{}", + oversized_not_text.chars().count() + ); + 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 961fee811..8b49b1f30 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 @@ -6,6 +6,13 @@ use serde_json::Value; use super::error::ToolPageRejection; +use crate::agent::runtime_state::truncate_agent_runtime_text; + +/// 字段给了非字符串值时,回显进诊断的那个值最多保留多少字符。 +/// +/// 类型错只需要让模型看出「传进来的是什么」,不必把整个对象或数组原样带进工具结果与 +/// 诊断 sidecar;`got` 会同时出现在 `to_user_msg` 和序列化后的 `error` 里。 +const NOT_TEXT_GOT_MAX_CHARS: usize = 120; /// 一个会被边界校验的文本字段。 pub(crate) trait ToolTextField: Copy { @@ -35,7 +42,10 @@ pub(crate) fn optional_text, E>( return Ok(None); }; let Some(value) = value.as_str() else { - return Err(field.not_text(value.to_string())); + return Err(field.not_text(truncate_agent_runtime_text( + &value.to_string(), + NOT_TEXT_GOT_MAX_CHARS, + ))); }; let value = value.trim(); if value.is_empty() {