"use strict";
/**
 * Schema Sanitizer
 *
 * Sanitizes tool input_schema to only include fields supported by Google's
 * Gemini/Vertex AI function_declarations Schema object.
 *
 * This sanitizer runs exclusively in the CLIProxy execution path (Gemini, Codex,
 * Antigravity, etc.), never for direct Anthropic API requests.
 *
 * Gemini supports a subset of OpenAPI 3.0 schema — fields outside this subset
 * (like "examples", "$ref", "oneOf", etc.) cause 400 errors.
 *
 * Reference: https://ai.google.dev/api/rest/v1beta/cachedContents#Schema
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.sanitizeToolSchemas = exports.sanitizeInputSchema = void 0;
/**
 * Fields supported by Gemini's function_declarations Schema object.
 * Source: https://ai.google.dev/api/rest/v1beta/cachedContents#Schema
 */
const GEMINI_SUPPORTED_SCHEMA_FIELDS = new Set([
    // Core
    'type',
    'format',
    'title',
    'description',
    'nullable',
    'example',
    'default',
    // Enum
    'enum',
    // Object
    'properties',
    'required',
    'minProperties',
    'maxProperties',
    // Array
    'items',
    'minItems',
    'maxItems',
    // String validation
    'minLength',
    'maxLength',
    'pattern',
    // Number validation
    'minimum',
    'maximum',
    // Composition (only anyOf supported, NOT oneOf/allOf/not)
    'anyOf',
    // Gemini-specific (non-standard OpenAPI)
    'propertyOrdering',
]);
/** Maximum recursion depth to prevent stack overflow */
const MAX_DEPTH = 100;
/**
 * Check if a key is supported by Gemini's Schema object.
 */
function isValidSchemaKey(key) {
    return GEMINI_SUPPORTED_SCHEMA_FIELDS.has(key);
}
/**
 * Recursively sanitize a JSON Schema object.
 * Removes fields unsupported by Gemini while preserving valid schema structure.
 *
 * @param schema The schema object to sanitize
 * @param path Current path for logging (e.g., "properties.foo.items")
 * @param removedPaths Accumulator for removed property paths
 * @param visited WeakSet to track visited objects and prevent circular references
 * @param depth Current recursion depth to prevent stack overflow
 * @returns Sanitized schema object
 */
function sanitizeSchemaRecursive(schema, path, removedPaths, visited = new WeakSet(), depth = 0) {
    // Guard: prevent stack overflow from deep nesting
    if (depth > MAX_DEPTH) {
        return schema;
    }
    // Handle non-objects (primitives, null)
    if (typeof schema !== 'object' || schema === null) {
        return schema;
    }
    // Guard: prevent infinite recursion from circular references
    if (visited.has(schema)) {
        return schema;
    }
    // Mark object as visited
    visited.add(schema);
    // Handle arrays
    if (Array.isArray(schema)) {
        return schema.map((item, index) => sanitizeSchemaRecursive(item, `${path}[${index}]`, removedPaths, visited, depth + 1));
    }
    // Handle objects
    const result = {};
    const obj = schema;
    for (const [key, value] of Object.entries(obj)) {
        const keyPath = path ? `${path}.${key}` : key;
        // Special handling for nested schema containers
        if (key === 'properties' && typeof value === 'object' && value !== null) {
            // Recurse into each property definition
            const sanitizedProps = {};
            for (const [propName, propSchema] of Object.entries(value)) {
                sanitizedProps[propName] = sanitizeSchemaRecursive(propSchema, `${keyPath}.${propName}`, removedPaths, visited, depth + 1);
            }
            result[key] = sanitizedProps;
            continue;
        }
        if (key === 'items') {
            // items can be object or array of objects
            result[key] = sanitizeSchemaRecursive(value, keyPath, removedPaths, visited, depth + 1);
            continue;
        }
        // anyOf is the only composition keyword Gemini supports
        if (key === 'anyOf' && Array.isArray(value)) {
            result[key] = value.map((item, index) => sanitizeSchemaRecursive(item, `${keyPath}[${index}]`, removedPaths, visited, depth + 1));
            continue;
        }
        // Check if this is a Gemini-supported field
        if (isValidSchemaKey(key)) {
            // "example" and "default" hold arbitrary values, not nested schemas — pass through as-is
            if (key === 'example' || key === 'default') {
                result[key] = value;
            }
            else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
                // Recurse for nested objects that might contain schemas
                result[key] = sanitizeSchemaRecursive(value, keyPath, removedPaths, visited, depth + 1);
            }
            else {
                result[key] = value;
            }
        }
        else {
            // Unsupported field — remove it
            removedPaths.push(keyPath);
        }
    }
    return result;
}
/**
 * Sanitize an input_schema object, removing fields unsupported by Gemini.
 *
 * @param inputSchema The tool's input_schema object
 * @returns Sanitization result with cleaned schema and metadata
 */
function sanitizeInputSchema(inputSchema) {
    // Preserve null/undefined as-is (graceful handling of missing schemas)
    if (inputSchema === null || inputSchema === undefined) {
        return {
            schema: inputSchema,
            removedCount: 0,
            removedPaths: [],
        };
    }
    // Non-object types (arrays, primitives) - return empty object
    if (typeof inputSchema !== 'object' || Array.isArray(inputSchema)) {
        return { schema: {}, removedCount: 0, removedPaths: [] };
    }
    const removedPaths = [];
    const sanitized = sanitizeSchemaRecursive(inputSchema, '', removedPaths);
    return {
        schema: sanitized,
        removedCount: removedPaths.length,
        removedPaths,
    };
}
exports.sanitizeInputSchema = sanitizeInputSchema;
/**
 * Sanitize an array of tools, cleaning their input_schema properties.
 *
 * @param tools Array of tool definitions
 * @returns Object with sanitized tools and total removed count
 */
function sanitizeToolSchemas(tools) {
    const removedByTool = [];
    let totalRemoved = 0;
    const sanitizedTools = tools.map((tool) => {
        // Guard against non-object elements
        if (typeof tool !== 'object' || tool === null) {
            return tool;
        }
        if (!tool.input_schema || typeof tool.input_schema !== 'object') {
            return tool;
        }
        const result = sanitizeInputSchema(tool.input_schema);
        if (result.removedCount > 0) {
            removedByTool.push({
                name: tool.name,
                removed: result.removedPaths,
            });
            totalRemoved += result.removedCount;
        }
        return {
            ...tool,
            input_schema: result.schema,
        };
    });
    return {
        tools: sanitizedTools,
        totalRemoved,
        removedByTool,
    };
}
exports.sanitizeToolSchemas = sanitizeToolSchemas;
//# sourceMappingURL=schema-sanitizer.js.map