diff --git a/src/agents/schema/clean-for-gemini.test.ts b/src/agents/schema/clean-for-gemini.test.ts new file mode 100644 index 00000000000..22fa7a4d162 --- /dev/null +++ b/src/agents/schema/clean-for-gemini.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from "vitest"; +import { cleanSchemaForGemini } from "./clean-for-gemini.js"; + +describe("cleanSchemaForGemini", () => { + it("coerces null properties to an empty object", () => { + const cleaned = cleanSchemaForGemini({ + type: "object", + properties: null, + }) as { type?: unknown; properties?: unknown }; + + expect(cleaned.type).toBe("object"); + expect(cleaned.properties).toEqual({}); + }); + + it("coerces non-object properties to an empty object", () => { + const cleaned = cleanSchemaForGemini({ + type: "object", + properties: "invalid", + }) as { properties?: unknown }; + + expect(cleaned.properties).toEqual({}); + }); + + it("coerces nested null properties while preserving valid siblings", () => { + const cleaned = cleanSchemaForGemini({ + type: "object", + properties: { + bad: { + type: "object", + properties: null, + }, + good: { + type: "string", + }, + }, + }) as { + properties?: { + bad?: { properties?: unknown }; + good?: { type?: unknown }; + }; + }; + + expect(cleaned.properties?.bad?.properties).toEqual({}); + expect(cleaned.properties?.good?.type).toBe("string"); + }); +}); diff --git a/src/agents/schema/clean-for-gemini.ts b/src/agents/schema/clean-for-gemini.ts index b416c32168e..669d8b9ac03 100644 --- a/src/agents/schema/clean-for-gemini.ts +++ b/src/agents/schema/clean-for-gemini.ts @@ -304,14 +304,20 @@ function cleanSchemaForGeminiWithDefs( continue; } - if (key === "properties" && value && typeof value === "object") { - const props = value as Record; - cleaned[key] = Object.fromEntries( - Object.entries(props).map(([k, v]) => [ - k, - cleanSchemaForGeminiWithDefs(v, nextDefs, refStack), - ]), - ); + if (key === "properties") { + if (value && typeof value === "object" && !Array.isArray(value)) { + const props = value as Record; + cleaned[key] = Object.fromEntries( + Object.entries(props).map(([k, v]) => [ + k, + cleanSchemaForGeminiWithDefs(v, nextDefs, refStack), + ]), + ); + } else { + // Guard malformed schemas (e.g. properties: null) that can trigger + // downstream Object.* crashes in strict provider validators. + cleaned[key] = {}; + } } else if (key === "items" && value) { if (Array.isArray(value)) { cleaned[key] = value.map((entry) =>