From f6c59d30b0215986e703ff38e2ce31dfea7d2a0a Mon Sep 17 00:00:00 2001 From: decolua Date: Thu, 3 Sep 2026 09:05:57 +0700 Subject: [PATCH] fix(gemini): convert prefixItems and ensure array items in schema sanitizer Co-Authored-By: Claude Code --- open-sse/translator/formats/gemini.js | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/open-sse/translator/formats/gemini.js b/open-sse/translator/formats/gemini.js index 6393a78b..8a965fee 100644 --- a/open-sse/translator/formats/gemini.js +++ b/open-sse/translator/formats/gemini.js @@ -14,6 +14,8 @@ export const UNSUPPORTED_SCHEMA_CONSTRAINTS = [ "uniqueItems", "contains", // 2020-12 keywords with no Gemini equivalent "unevaluatedProperties", "unevaluatedItems", "contentSchema", + // Tuple-array keywords; converted to items first, leftovers stripped + "prefixItems", "additionalItems", // Claude rejects these in VALIDATED mode "default", "examples", // JSON Schema meta keywords @@ -308,6 +310,37 @@ function ensureObjectType(obj) { for (const v of Object.values(obj)) if (v && typeof v === "object") ensureObjectType(v); } +// Convert prefixItems (tuple validation) to items — Gemini cannot express tuples, +// and a type:"array" schema without items is rejected with "missing field" +function convertPrefixItems(obj) { + if (!obj || typeof obj !== "object") return; + + if (Array.isArray(obj.prefixItems) && obj.prefixItems.length > 0) { + const variants = obj.prefixItems.filter(s => s && s.type !== "null"); + if (!obj.items && variants.length === 1) { + obj.items = variants[0]; + } else if (!obj.items && variants.length > 1) { + obj.items = { anyOf: variants }; + } + delete obj.prefixItems; + } + + for (const value of Object.values(obj)) { + if (value && typeof value === "object") { + convertPrefixItems(value); + } + } +} + +// Gemini requires items on every type:"array" schema — fill a permissive placeholder +function ensureArrayItems(obj) { + if (!obj || typeof obj !== "object") return; + if (obj.type === "array" && !obj.items) { + obj.items = { type: "string" }; + } + for (const v of Object.values(obj)) if (v && typeof v === "object") ensureArrayItems(v); +} + // Clean JSON Schema for Antigravity API compatibility - removes unsupported keywords recursively export function cleanJSONSchemaForAntigravity(schema) { if (!schema || typeof schema !== "object") return schema; @@ -321,11 +354,13 @@ export function cleanJSONSchemaForAntigravity(schema) { // Phase 2: Flatten complex structures mergeAllOf(cleaned); + convertPrefixItems(cleaned); flattenAnyOfOneOf(cleaned); flattenTypeArrays(cleaned); // Phase 2.5: Infer missing type=object when properties exist (Gemini requirement) ensureObjectType(cleaned); + ensureArrayItems(cleaned); // Phase 3: Remove all unsupported keywords at ALL levels (including inside arrays) removeUnsupportedKeywords(cleaned, UNSUPPORTED_SCHEMA_CONSTRAINTS);