fix(tools): preserve string param in MCP, optional types in JS, and optimize input IPC (#1125) - #1126
Conversation
CATMIAOZHI
left a comment
There was a problem hiding this comment.
BLOCK:发现 5 项 P2,具体触发条件和修正建议见行内评论。
审计提交:80b7d3bc83f186d056f388e7a52005011b480e54。已完成独立工作树全 diff 与调用链审查、独立 agent 复核及 diff --check;审计前已请求 CATMIAOZHI review。
验证范围:当前上游仓库该 HEAD 的 check-runs 为 0,审计前无 review 线程。现有 PR workflow 仅匹配 development,而本 PR 目标为 dev;构建/测试 push workflow 匹配 main。已核对仓库 Actions Variables(GITEE_OWNER、GITEE_REPO)及工作流任务。未获得本次 HEAD 的可核实构建/单测成功证据。遵守目标仓库 AGENTS.md,本次未运行本地编译、测试、Lint 或真机验证;未修改源码。
本 PR 的完整差异包含 #1120 的滚动改动,相关三个问题也适用于此 HEAD,已一并标注。MCP 显式 string 保留本身未发现新增问题。
| rawValue.equals("null", ignoreCase = true) || | ||
| rawValue.equals("undefined", ignoreCase = true) | ||
|
|
||
| if (!isRequired && isBlankOrNull) { |
There was a problem hiding this comment.
[P2] 保留可选 string 参数的合法原文
此分支没有限定参数类型,所以 optional string 的空白文本、字面字符串 "null"/"undefined" 也被改成 null。例如内置 system_tools.bluetooth_send 的 text 就是 optional string,并直接把 params.text 传给蓝牙工具;用户要发送字面文本 "undefined" 时,内容会在进入 JS 前丢失。请让 string 继续原样透传,将空值处理限定到确实需要解析的非字符串类型。
| val pastedText = | ||
| if (convertLongPastedTextToFile && clipboardText != null) { | ||
| extractClipboardPastedText(userMessage, value, clipboardText) | ||
| if (convertLongPastedTextToFile && pastedLength > longPastedTextFileThreshold) { |
There was a problem hiding this comment.
[P2] 长粘贴预判应计入被替换的选区
这里比较的是文本净增长量,遗漏了替换选区的正常粘贴。例如阈值 1000,选中已有 2000 字并粘贴 2500 字,净增长仅 500,会跳过剪贴板提取,长文本不再转附件。原有 extractClipboardPastedText 明确支持选区替换。请在预判插入长度时加回被替换选区长度,并保留后续准确提取与阈值判断。
| } | ||
| val deltaDp = deltaPx / densityDpiRatio | ||
| val calculatedMs = ((deltaDp / maxSpeedDpPerSecond) * 1000f).toLong() | ||
| return calculatedMs.coerceIn(minDurationMs, maxDurationMs).toInt() |
There was a problem hiding this comment.
[P2] 不要用 1 秒时长上限突破用户设置的滚动速度
当流式输出积压 500 dp、用户选择 50 dp/s 时,计算需要 10 秒,但这里截成 1000 ms,后面的 LinearEasing 动画仍滚完全部距离,实际达到 500 dp/s。ChatArea 和 AIChatScreen 的追赶滚动都会走此函数,因此大块输出时恰好无法保持设置的上限。请保留满足限速所需的时长,或限制每段滚动位移。
| val isTargetAi = targetMessage?.sender == "ai" | ||
| val isNonStreamingAi = isTargetAi && | ||
| targetMessage?.contentStream == null && | ||
| targetMessage?.timestamp !in streamedAiMessageTimestamps |
There was a problem hiding this comment.
[P2] 根据实际输出模式识别非流式回复
普通聊天关闭 Waifu、开启“禁用流式输出”时,AIMessageManager 虽将 stream=false 传入请求,仍无条件调用 shareRevisable;MessageProcessingDelegate 随后把带 contentStream 的占位 AI 消息加入界面。下面的 effect 因此会将 timestamp 记入 streamedAiMessageTimestamps,完成后即使清掉 contentStream,该记录仍在,这里也不能进入顶部定位分支。请传递实际请求的流式模式,避免把 UI 使用共享流当成网络流式输出的证据。
| if (isNonStreamingAi && enableNonStreamingScrollToTop) { | ||
| val targetOffset = | ||
| targetAnchor.absoluteTopPx.roundToInt().coerceIn(0, scrollState.maxValue) | ||
| scrollState.animateScrollTo(targetOffset) |
There was a problem hiding this comment.
[P2] 保留用户显式“滚动到底部”的导航行为
默认启用该选项后,重开以长 AI 回答结尾的历史并向上拖动,再点击底部按钮:ChatScrollNavigator 先启动到底动画并设置 autoScroll=true;后者触发 ChatArea 上方 effect,将最新消息设为 pending。历史消息没有 contentStream,且本次进入的 streamed 集合为空,于是这里又启动到消息开头的动画,打断或覆盖用户的到底请求。请把新非流式回复的自动顶部定位与用户显式到底导航区分开。
80b7d3b to
0dd626a
Compare
0dd626a to
5e2b453
Compare
变更说明 / Description
本 PR 针对工具参数类型转换与输入性能方面的三个问题进行了集中修复与优化:
string的参数在值为纯数字/坐标字符串时被错误强转为 JSON number 的问题;required: false的非字符串参数(number / integer / boolean / array / object)在留空或传入空白时无脑强转导致抛出类型异常的问题;背景与动机 / Context and motivation
在
MCPToolParameter.kt的smartConvert中,when (typeName?.lowercase())分支漏掉了"string",且else分支直接进入正则猜测。当 MCP 服务端声明参数类型为string,而传入的值为纯数字或浮点坐标(如"32.035599")时,该参数被强转为 Double/Long,序列化为 JSON number 后导致对参数类型严格校验的服务端(如滴滴出行 MCP 等)报错“缺少必填参数”。在
JsToolManager.kt中,convertToolParameters遍历参数执行convertToolParameterValue时未校验参数是否必填(required: false)。当模型在调用工具时将可选参数留空("")或传null时,非字符串类型(number / integer / boolean / array / object)解析失败直接抛出ToolParameterConversionException,导致插件调用失败,迫使开发者只能在 metadata 中把可选参数妥协声明为string再在 JS 侧手动强转。在
AIChatScreen.kt的handleUserMessageChange中,每次按键输入都会无条件调用clipboardManager.primaryClip。该方法属于跨进程同步 Binder IPC,单次开销可达数毫秒至十数毫秒。在用户打字或长文本编辑场景下,高频 IPC 严重阻塞主线程,配合 Compose 文本排版引发掉帧和打字粘滞。改动范围 / Changes
MCPToolParameter.kt):smartConvert与convertParameterValue中显式增加"string" -> value分支,严格保护声明为 string 的纯数字/布尔等字符串原样透传;null, "")时才触发,其余显式未知类型保持原样,不再盲目正则强转。JsToolManager.kt):convertToolParameters中,对声明为required = false且输入值为空白、"null"或"undefined"的参数,直接赋null,不再调用convertToolParameterValue触发解析异常。AIChatScreen.kt):value.text.length - userMessage.text.length > longPastedTextFileThreshold(增量真正达到大段粘贴阈值)时,才查询系统剪贴板检测粘贴附件;MCPToolParameterTest.kt,覆盖坐标字符串保留、布尔字符串保留、未指定类型猜测以及显式数字类型转换。明确不包含:
兼容性与风险 / Compatibility and risks
required: false插件在可选参数未填时能正确获得null而不是报错;关联 Issue / Related issue
Resolves #1125
验证方式 / Verification