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 8618e5644..0fb89c724 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 @@ -34,6 +34,8 @@ pub(crate) trait ToolTextField: Copy { } /// 必填文本字段:缺省、`null`、空串都算空值。 +/// +/// 契约同 [`optional_text`]:调用方必须先确认入参是对象。 pub(crate) fn required_text, E>( arguments: &Value, field: F, @@ -42,10 +44,18 @@ pub(crate) fn required_text, E>( } /// 可选文本字段:缺省或 `null` 视为未提供;给了就必须在该字段自己的边界内。 +/// +/// 调用方必须先用 `ToolArgumentsRejection::check` 确认入参是 JSON 对象:非对象时 +/// `Value::get` 恒为 `None`,这里会静默当成「没给这个字段」,把 `NotObject` 判成 `empty`。 +/// 所有调用点现在都先跑 `check`,`debug_assert` 只保证以后不会有人绕过。 pub(crate) fn optional_text, E>( arguments: &Value, field: F, ) -> Result, E> { + debug_assert!( + arguments.is_object(), + "optional_text 的调用方必须先跑 ToolArgumentsRejection::check" + ); let Some(value) = arguments.get(field.key()).filter(|value| !value.is_null()) else { return Ok(None); }; @@ -76,12 +86,19 @@ pub(crate) struct ToolPage { /// /// 调用点必须保证 `default_limit <= max_limit`:否则省略 `limit` 的请求会拿一个本身就越界的 /// 默认值去撞上限、每次都失败。这里用 `debug_assert` 让配错在测试期就炸出来。 +/// +/// 同样要求调用方先确认入参是对象:非对象时 `offset`/`limit` 都会被当成没给,静默回落到默认 +/// 分页,把 `NotObject` 掩盖掉。 pub(crate) fn page( arguments: &Value, default_limit: usize, max_limit: usize, max_offset: Option, ) -> Result { + debug_assert!( + arguments.is_object(), + "page 的调用方必须先跑 ToolArgumentsRejection::check" + ); debug_assert!(default_limit <= max_limit); let offset = arguments .get("offset")