Appearance
功能规格说明:内置工具的参数校验
创建日期: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 后再调一次,确认正常执行。
验收场景:
- 假设 一个必填参数没有出现在调用参数里,当 该工具被调用时,则 返回失败的工具结果,内容为
The required parameter `taskId` is missing,且工具实现没有被执行。 - 假设 调用参数里带了声明之外的多余键,当 该工具被调用时,则 返回
An unexpected parameter `id` was provided——参数名写错正是靠这一条被点出来。 - 假设 参数值类型与声明不符(如声明
number却传"20"),当 该工具被调用时,则 返回The parameter `offset` type is expected as `number` but provided as `string`。 - 假设 参数值不在声明的
enum内,当 该工具被调用时,则 返回The parameter `mode` is invalid: expected one of …。 - 假设 一个可选参数传了
null而声明只允许string,当 该工具被调用时,则 与类型不符同处理(null不算"缺失");声明允许null时正常通过。 - 假设 调用参数完全合法(包括可选参数缺省、以及声明为自由键值对的对象参数,如
AskUserQuestion的metadata),当 该工具被调用时,则 行为与本规格之前完全一致。 - 假设 一次参数校验失败,当 它返回给模型时,则 它只是一条可恢复的工具结果:对话继续、模型下一轮可以重试,不抛异常中断回合。
- 假设 被调用的是 MCP 工具,当 参数不完整时,则 不由 wave 校验(参数契约属于 MCP 服务端,wave 只透传),行为与本规格之前一致。
- 假设 被调用的是
buildTool()/customTools注册的自定义工具,当 参数不完整时,则 不由 wave 校验——其 schema 由 SDK 用户定义,行为保持不变。 - 假设 一次调用参数有误,当 校验失败时,则 不触发 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 补上对应属性,而不是放宽全局。