// Gemini helper functions for translator // Unsupported JSON Schema constraints that should be removed for Antigravity // Reference: CLIProxyAPI/internal/util/gemini_schema.go (removeUnsupportedKeywords) export const UNSUPPORTED_SCHEMA_CONSTRAINTS = [ // Basic constraints (not supported by Gemini API) "minLength", "maxLength", "exclusiveMinimum", "exclusiveMaximum", "pattern", "minItems", "maxItems", "format", // Claude rejects these in VALIDATED mode "default", "examples", // JSON Schema meta keywords "$schema", "$defs", "definitions", "const", "$ref", // Object validation keywords (not supported) "additionalProperties", "propertyNames", "patternProperties", // Complex schema keywords (handled by flattenAnyOfOneOf/mergeAllOf) "anyOf", "oneOf", "allOf", "not", // Dependency keywords (not supported) "dependencies", "dependentSchemas", "dependentRequired", // Other unsupported keywords "title", "if", "then", "else", "contentMediaType", "contentEncoding" ]; // Default safety settings export const DEFAULT_SAFETY_SETTINGS = [ { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "OFF" }, { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "OFF" }, { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "OFF" }, { category: "HARM_CATEGORY_HARASSMENT", threshold: "OFF" }, { category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "OFF" } ]; // Convert OpenAI content to Gemini parts export function convertOpenAIContentToParts(content) { const parts = []; if (typeof content === "string") { parts.push({ text: content }); } else if (Array.isArray(content)) { for (const item of content) { if (item.type === "text") { parts.push({ text: item.text }); } else if (item.type === "image_url" && item.image_url?.url?.startsWith("data:")) { const url = item.image_url.url; const commaIndex = url.indexOf(","); if (commaIndex !== -1) { const mimePart = url.substring(5, commaIndex); // skip "data:" const data = url.substring(commaIndex + 1); const mimeType = mimePart.split(";")[0]; parts.push({ inlineData: { mime_type: mimeType, data: data } }); } } } } return parts; } // Extract text content from OpenAI content export function extractTextContent(content) { if (typeof content === "string") return content; if (Array.isArray(content)) { return content.filter(c => c.type === "text").map(c => c.text).join(""); } return ""; } // Try parse JSON safely export function tryParseJSON(str) { if (typeof str !== "string") return str; try { return JSON.parse(str); } catch { return null; } } // Generate request ID export function generateRequestId() { return `agent-${crypto.randomUUID()}`; } // Generate session ID export function generateSessionId() { return `-${Math.floor(Math.random() * 9000000000000000000)}`; } // Generate project ID export function generateProjectId() { const adjectives = ["useful", "bright", "swift", "calm", "bold"]; const nouns = ["fuze", "wave", "spark", "flow", "core"]; const adj = adjectives[Math.floor(Math.random() * adjectives.length)]; const noun = nouns[Math.floor(Math.random() * nouns.length)]; return `${adj}-${noun}-${crypto.randomUUID().slice(0, 5)}`; } // Clean JSON Schema for Antigravity API compatibility - removes unsupported keywords recursively export function cleanJSONSchemaForAntigravity(schema) { if (!schema || typeof schema !== "object") return schema; // Handle anyOf/oneOf - extract the first non-null schema if (schema.anyOf && Array.isArray(schema.anyOf)) { const nonNullSchema = schema.anyOf.find(s => s.type !== "null" && s.type !== null); if (nonNullSchema) { const baseSchema = { ...nonNullSchema }; // Copy other properties from parent schema (except unsupported ones) for (const [key, value] of Object.entries(schema)) { if (!UNSUPPORTED_SCHEMA_CONSTRAINTS.includes(key)) { baseSchema[key] = value; } } return cleanJSONSchemaForAntigravity(baseSchema); } } if (schema.oneOf && Array.isArray(schema.oneOf)) { const nonNullSchema = schema.oneOf.find(s => s.type !== "null" && s.type !== null); if (nonNullSchema) { const baseSchema = { ...nonNullSchema }; // Copy other properties from parent schema (except unsupported ones) for (const [key, value] of Object.entries(schema)) { if (!UNSUPPORTED_SCHEMA_CONSTRAINTS.includes(key)) { baseSchema[key] = value; } } return cleanJSONSchemaForAntigravity(baseSchema); } } const cleaned = Array.isArray(schema) ? [] : {}; for (const [key, value] of Object.entries(schema)) { if (UNSUPPORTED_SCHEMA_CONSTRAINTS.includes(key)) continue; // Handle type array like ["string", "null"] - Gemini only supports single type if (key === "type" && Array.isArray(value)) { const nonNullType = value.find(t => t !== "null") || "string"; cleaned[key] = nonNullType; continue; } if (value && typeof value === "object") { cleaned[key] = cleanJSONSchemaForAntigravity(value); } else { cleaned[key] = value; } } // Cleanup required fields - only keep fields that exist in properties if (cleaned.required && Array.isArray(cleaned.required) && cleaned.properties) { const validRequired = cleaned.required.filter(field => Object.prototype.hasOwnProperty.call(cleaned.properties, field) ); if (validRequired.length === 0) { delete cleaned.required; } else { cleaned.required = validRequired; } } // Add placeholder for empty object schemas (Antigravity requirement) if (cleaned.type === "object") { if (!cleaned.properties || Object.keys(cleaned.properties).length === 0) { cleaned.properties = { reason: { type: "string", description: "Brief explanation of why you are calling this tool" } }; cleaned.required = ["reason"]; } } return cleaned; }