From 0e60ecb7698cc6f69104b56bd63cbc58c73db50d 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 17:49:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=B1=E4=BA=AB=E5=85=A5=E5=8F=82=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=99=A8=E8=A1=A5=E3=80=8C=E5=85=A5=E5=8F=82=E5=BF=85?= =?UTF-8?q?=E9=A1=BB=E6=98=AF=E5=AF=B9=E8=B1=A1=E3=80=8D=E7=9A=84=E5=A5=91?= =?UTF-8?q?=E7=BA=A6=E6=96=AD=E8=A8=80=E4=B8=8E=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - optional_text/required_text/page 加 debug_assert!(arguments.is_object()):非对象时 Value::get 恒为 None,会把 NotObject 静默判成「没给字段」或默认分页 - 文档写明调用方必须先跑 ToolArgumentsRejection::check;所有现有调用点已满足,release 行为不变 验证:cargo test --bin genarrative-ai-game-creator-shell -- agent::direct_tool_bridge:: agent::direct_tools_mcp:: 72 passed / 0 failed;cargo fmt --check 通过 --- .../src-tauri/src/agent/tool/arguments.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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")