Skip to content

功能规格说明:内置工具的参数校验 ​

创建日期:2026-10-19

用户场景与测试 (必填) ​

用户故事:内置工具调用前先校验参数(优先级:P1) ​

作为模型,我希望参数写错时立刻被告知哪个参数错了、错在哪,以便下一轮自己改对;作为用户,我希望参数错误不要变成静默的错事——少传 content 就把 undefined 当内容写进文件,参数名写错就拿 undefined 去查任务,最后报出来的是一句与真正原因无关的下游怪象。

为什么是这个优先级:参数错误是模型最高频的一类失误,而 wave 的每个内置工具都把自己要的参数声明在 config.function.parameters(JSON Schema)里,这份声明只用来给模型看,运行时从不校验。错误的后果因此不是"报错"而是"下沉":工具内部某个字段成了 undefined,要么做错事(写空内容、写到错误路径),要么在很远的地方报一句读不出原因的错误(Task #undefined not found)。补齐这一层的成本远低于它挡住的问题。

参照物:Claude Code 在工具执行前做四层检查(JSON 解析 → 工具自己的 coerceInput 兼容层 → zod schema 校验 → 工具自己的语义校验),任何一层失败都不抛异常,而是返回一条带 is_error 的工具结果,模型下一轮可自行修正。本规格对齐它的第三层与文案,不抄前两层。

独立测试:调用 TaskUpdate 只传 { "id": "2", "status": "completed" },确认返回的错误点明「缺少 taskId」且任务未被修改;补齐 taskId 后再调一次,确认正常执行。

验收场景:

  1. 假设 一个必填参数没有出现在调用参数里,当 该工具被调用时,则 返回失败的工具结果,内容为 The required parameter `taskId` is missing,且工具实现没有被执行。
  2. 假设 调用参数里带了声明之外的多余键,当 该工具被调用时,则 返回 An unexpected parameter `id` was provided——参数名写错正是靠这一条被点出来。
  3. 假设 参数值类型与声明不符(如声明 number 却传 "20"),当 该工具被调用时,则 返回 The parameter `offset` type is expected as `number` but provided as `string` 。
  4. 假设 参数值不在声明的 enum 内,当 该工具被调用时,则 返回 The parameter `mode` is invalid: expected one of …。
  5. 假设 一个可选参数传了 null 而声明只允许 string,当 该工具被调用时,则 与类型不符同处理(null 不算"缺失");声明允许 null 时正常通过。
  6. 假设 调用参数完全合法(包括可选参数缺省、以及声明为自由键值对的对象参数,如 AskUserQuestion 的 metadata),当 该工具被调用时,则 行为与本规格之前完全一致。
  7. 假设 一次参数校验失败,当 它返回给模型时,则 它只是一条可恢复的工具结果:对话继续、模型下一轮可以重试,不抛异常中断回合。
  8. 假设 被调用的是 MCP 工具,当 参数不完整时,则 不由 wave 校验(参数契约属于 MCP 服务端,wave 只透传),行为与本规格之前一致。
  9. 假设 被调用的是 buildTool() / customTools 注册的自定义工具,当 参数不完整时,则 不由 wave 校验——其 schema 由 SDK 用户定义,行为保持不变。
  10. 假设 一次调用参数有误,当 校验失败时,则 不触发 PreToolUse hook、也不进入权限判定——参数错的调用不该弹权限框、也不该在 hook 里留下痕迹。

口径与边界:

  • 真相源是工具自己声明的 config.function.parameters,不新增第二份 schema:不引入 zod 再导出 JSON Schema 的双写形态(CC 用 zod 是因为它的 schema 本来就以 zod 定义;wave 反过来)。
  • 支持的子集:type、required、enum、未知键、嵌套 object 的 properties、数组 items 的逐元素类型。oneOf / anyOf / $ref / 数值区间等不解释——解释不了的声明不拦截,避免把"没实现的 constraint"读成"校验通过"以外的含义。
  • 未知键默认报错(对齐 CC 的 zod strict 语义):schema 显式声明 additionalProperties: true 时放开;声明为对象 schema(如 metadata 的 {type: "string"})时按其校验值,不按"未知键"处理。
  • 文案对齐 Claude Code:三句固定形态(缺失 / 多余 / 类型不符)+ 一句 enum 与其它取值错误,汇总成 `${toolName} failed due to the following issue(s):` 加逐行明细;一次调用里的问题全部列出,不只报第一个。
  • 不做 coerceInput 那类兼容层:先把真实错误暴露出来,等有了校验失败的数据再决定兼容什么,避免"为兼容而兼容"。
  • 校验失败不产生权限决策、不进 hook:它发生在工具执行之前,不是一次被拒绝的权限请求。

边界情况 ​

  • 声明之外但被工具读取的参数:本规格上线前已逐个核对过,内置工具读取的每个参数都在自己的 parameters 里声明过(agentTool 的 availableSubagents 属于 prompt 入参,不来自模型)。新增工具若读了未声明的参数,会被这一层挡住——这是有意的:要么补声明,要么改读法。
  • 工具自己注入的内部字段:只在校验前不增删参数;工具内部的临时字段由工具自己处理。
  • 校验与 additionalProperties 缺省:缺省视为"只认声明过的键"(严格),与 CC 一致;如果将来某个工具的合法调用因此被误伤,正确修法是给该工具的 schema 补上对应属性,而不是放宽全局。