mirror of https://github.com/openclaw/openclaw.git
Agents: clarify invalid required-param hints
This commit is contained in:
parent
ed5ff7f84f
commit
c1cf0691c9
|
|
@ -4,6 +4,10 @@ Docs: https://docs.openclaw.ai
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/tools: include value-shape hints in missing-parameter tool errors so dropped, empty-string, and wrong-type write payloads are easier to diagnose from logs. (#55317) Thanks @priyansh19.
|
||||
|
||||
## 2026.4.2-beta.1
|
||||
|
||||
### Breaking
|
||||
|
|
|
|||
|
|
@ -47,6 +47,40 @@ describe("assertRequiredParams", () => {
|
|||
).toThrow(/\(received: file_path\)[^,]/);
|
||||
});
|
||||
|
||||
it("shows empty-string values for present params that still fail validation", () => {
|
||||
expect(() =>
|
||||
assertRequiredParams(
|
||||
{ path: "/tmp/a.txt", content: " " },
|
||||
[
|
||||
{ keys: ["path", "file_path"], label: "path alias" },
|
||||
{ keys: ["content"], label: "content" },
|
||||
],
|
||||
"write",
|
||||
),
|
||||
).toThrow(/\(received: path, content=<empty-string>\)/);
|
||||
});
|
||||
|
||||
it("shows wrong-type values for present params that still fail validation", async () => {
|
||||
const tool = wrapToolParamNormalization(
|
||||
{
|
||||
name: "write",
|
||||
label: "write",
|
||||
description: "write a file",
|
||||
parameters: {},
|
||||
execute: vi.fn(),
|
||||
},
|
||||
CLAUDE_PARAM_GROUPS.write,
|
||||
);
|
||||
await expect(
|
||||
tool.execute(
|
||||
"id",
|
||||
{ file_path: "test.txt", content: { unexpected: true } },
|
||||
new AbortController().signal,
|
||||
vi.fn(),
|
||||
),
|
||||
).rejects.toThrow(/\(received: (?:path, content=<object>|content=<object>, path)\)/);
|
||||
});
|
||||
|
||||
it("includes multiple received keys when several params are present", () => {
|
||||
expect(() =>
|
||||
assertRequiredParams(
|
||||
|
|
|
|||
|
|
@ -13,6 +13,39 @@ function parameterValidationError(message: string): Error {
|
|||
return new Error(`${message}.${RETRY_GUIDANCE_SUFFIX}`);
|
||||
}
|
||||
|
||||
function describeReceivedParamValue(value: unknown, allowEmpty = false): string | undefined {
|
||||
if (value === undefined || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
if (allowEmpty || value.trim().length > 0) {
|
||||
return undefined;
|
||||
}
|
||||
return "<empty-string>";
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return "<array>";
|
||||
}
|
||||
return `<${typeof value}>`;
|
||||
}
|
||||
|
||||
function formatReceivedParamHint(
|
||||
record: Record<string, unknown>,
|
||||
groups: readonly RequiredParamGroup[],
|
||||
): string {
|
||||
const allowEmptyKeys = new Set(
|
||||
groups.filter((group) => group.allowEmpty).flatMap((group) => group.keys),
|
||||
);
|
||||
const received = Object.keys(record).flatMap((key) => {
|
||||
const detail = describeReceivedParamValue(record[key], allowEmptyKeys.has(key));
|
||||
if (record[key] === undefined || record[key] === null) {
|
||||
return [];
|
||||
}
|
||||
return [detail ? `${key}=${detail}` : key];
|
||||
});
|
||||
return received.length > 0 ? ` (received: ${received.join(", ")})` : "";
|
||||
}
|
||||
|
||||
export const CLAUDE_PARAM_GROUPS = {
|
||||
read: [{ keys: ["path", "file_path", "filePath", "file"], label: "path alias" }],
|
||||
write: [
|
||||
|
|
@ -275,10 +308,7 @@ export function assertRequiredParams(
|
|||
if (missingLabels.length > 0) {
|
||||
const joined = missingLabels.join(", ");
|
||||
const noun = missingLabels.length === 1 ? "parameter" : "parameters";
|
||||
const receivedKeys = Object.keys(record).filter(
|
||||
(k) => record[k] !== undefined && record[k] !== null,
|
||||
);
|
||||
const receivedHint = receivedKeys.length > 0 ? ` (received: ${receivedKeys.join(", ")})` : "";
|
||||
const receivedHint = formatReceivedParamHint(record, groups);
|
||||
throw parameterValidationError(`Missing required ${noun}: ${joined}${receivedHint}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue