Skip to content

Commit cf58519

Browse files
authored
Merge 79ecfc2 into 4010b81
2 parents 4010b81 + 79ecfc2 commit cf58519

2 files changed

Lines changed: 40 additions & 0 deletions

File tree

packages/llm-core/src/validation.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,29 @@ describe("validateToolArguments", () => {
3939
}),
4040
).toThrow(/Validation failed for tool "decimal-tool"/);
4141
});
42+
43+
it("preserves null in anyOf [{type: string}, {type: null}] without coercing to empty string (#96716)", () => {
44+
const tool = {
45+
name: "nullable-tool",
46+
description: "test tool",
47+
parameters: {
48+
type: "object",
49+
properties: {
50+
insight_id: { anyOf: [{ type: "string" }, { type: "null" }] },
51+
cluster_name: { type: "string" },
52+
},
53+
required: ["cluster_name"],
54+
additionalProperties: false,
55+
},
56+
} as Tool;
57+
58+
expect(
59+
validateToolArguments(tool, {
60+
type: "toolCall",
61+
id: "call-1",
62+
name: "nullable-tool",
63+
arguments: { insight_id: null, cluster_name: "testenv" },
64+
}),
65+
).toEqual({ insight_id: null, cluster_name: "testenv" });
66+
});
4267
});

packages/llm-core/src/validation.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,21 @@ function applySchemaArrayCoercion(value: unknown[], schema: JsonSchemaObject): v
205205
}
206206

207207
function coerceWithUnionSchema(value: unknown, schemas: JsonSchemaObject[]): unknown {
208+
// When value is null, check if any union member accepts null directly
209+
// (type: "null") before falling through to coercion. Without this check,
210+
// anyOf [{type: "string"}, {type: "null"}] coerces null → "" via the
211+
// string branch and never reaches the null branch.
212+
if (value === null) {
213+
for (const schema of schemas) {
214+
const types = getSchemaTypes(schema);
215+
if (types.includes("null")) {
216+
const validator = getSubSchemaValidator(schema);
217+
if (!validator || validator.Check(value)) {
218+
return value;
219+
}
220+
}
221+
}
222+
}
208223
for (const schema of schemas) {
209224
const candidate = structuredClone(value);
210225
const coerced = coerceWithJsonSchema(candidate, schema);

0 commit comments

Comments
 (0)