UNPKG

superaugment

Version:

Enterprise-grade MCP server with world-class C++ analysis, robust error handling, and production-ready architecture for VS Code Augment

434 lines 14.9 kB
/** * SuperAugment Schema Converter * * Provides robust and maintainable conversion between Zod schemas and JSON Schema * for MCP protocol compatibility. Designed to be simple, reliable, and extensible. */ import { z } from 'zod'; import { logger } from './logger.js'; import { ErrorCode, ErrorSeverity, SuperAugmentError, } from '../errors/ErrorTypes.js'; /** * Default conversion options */ const DEFAULT_OPTIONS = { includeDescriptions: true, includeDefaults: true, strictMode: false, maxDepth: 10, }; /** * Robust Schema Converter for Zod to JSON Schema conversion */ export class SchemaConverter { conversionCache = new Map(); stats = { totalProperties: 0, successfulConversions: 0, failedConversions: 0, unsupportedTypes: [], conversionTime: 0, }; /** * Convert Zod schema to JSON Schema */ convertZodToJsonSchema(zodSchema, options = {}) { const startTime = Date.now(); const opts = { ...DEFAULT_OPTIONS, ...options }; try { this.resetStats(); const result = this.convertZodSchemaInternal(zodSchema, opts, 0); this.stats.conversionTime = Date.now() - startTime; logger.debug('Schema conversion completed', { stats: this.stats, options: opts, }); return result; } catch (error) { this.stats.conversionTime = Date.now() - startTime; this.stats.failedConversions++; throw new SuperAugmentError(`Schema conversion failed: ${error instanceof Error ? error.message : 'Unknown error'}`, ErrorCode.VALIDATION_FAILED, ErrorSeverity.HIGH, { additionalInfo: { stats: this.stats, options: opts } }, false, error instanceof Error ? error : undefined); } } /** * Convert Zod type to JSON Schema property */ convertZodTypeToProperty(zodType, options = {}) { const opts = { ...DEFAULT_OPTIONS, ...options }; return this.convertZodTypeInternal(zodType, opts, 0); } /** * Get conversion statistics */ getStats() { return { ...this.stats }; } /** * Clear conversion cache */ clearCache() { this.conversionCache.clear(); logger.debug('Schema conversion cache cleared'); } /** * Internal schema conversion implementation */ convertZodSchemaInternal(zodSchema, options, depth) { if (depth > options.maxDepth) { throw new SuperAugmentError(`Maximum conversion depth exceeded: ${options.maxDepth}`, ErrorCode.VALIDATION_FAILED, ErrorSeverity.HIGH, { additionalInfo: { depth, maxDepth: options.maxDepth } }, false); } // Handle ZodObject specifically if (zodSchema instanceof z.ZodObject) { return this.convertZodObject(zodSchema, options, depth); } // Handle other schema types that can be converted to objects const property = this.convertZodTypeInternal(zodSchema, options, depth); if (property.type === 'object' && property.properties) { const result = { type: 'object', properties: property.properties, additionalProperties: false, }; if (property.required) { result.required = property.required; } return result; } // Fallback: wrap non-object types in a generic object return { type: 'object', properties: { value: property, }, required: ['value'], additionalProperties: false, }; } /** * Convert ZodObject to JSON Schema */ convertZodObject(zodObject, options, depth) { const properties = {}; const required = []; const shape = zodObject.shape; for (const [key, zodType] of Object.entries(shape)) { try { this.stats.totalProperties++; const property = this.convertZodTypeInternal(zodType, options, depth + 1); properties[key] = property; // Check if property is required (not optional or has default) if (this.isRequiredProperty(zodType)) { required.push(key); } this.stats.successfulConversions++; } catch (error) { this.stats.failedConversions++; logger.warn(`Failed to convert property '${key}'`, { error }); if (options.strictMode) { throw error; } // Fallback property in non-strict mode properties[key] = { type: 'string', description: `Failed to convert property: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } const result = { type: 'object', properties, additionalProperties: false, }; if (required.length > 0) { result.required = required; } return result; } /** * Convert individual Zod type to JSON Schema property */ convertZodTypeInternal(zodType, options, depth) { if (depth > options.maxDepth) { return { type: 'string', description: 'Maximum depth exceeded', }; } // Check cache first const cacheKey = this.getCacheKey(zodType); if (this.conversionCache.has(cacheKey)) { return this.conversionCache.get(cacheKey); } const property = this.convertZodTypeCore(zodType, options, depth); // Cache the result this.conversionCache.set(cacheKey, property); return property; } /** * Core Zod type conversion logic */ convertZodTypeCore(zodType, options, depth) { const typeName = zodType._def?.typeName; switch (typeName) { case 'ZodString': return this.convertZodString(zodType, options); case 'ZodNumber': return this.convertZodNumber(zodType, options); case 'ZodBoolean': return this.convertZodBoolean(zodType, options); case 'ZodArray': return this.convertZodArray(zodType, options, depth); case 'ZodObject': return this.convertZodObjectToProperty(zodType, options, depth); case 'ZodEnum': return this.convertZodEnum(zodType, options); case 'ZodNativeEnum': return this.convertZodNativeEnum(zodType, options); case 'ZodLiteral': return this.convertZodLiteral(zodType, options); case 'ZodUnion': return this.convertZodUnion(zodType, options, depth); case 'ZodOptional': return this.convertZodOptional(zodType, options, depth); case 'ZodDefault': return this.convertZodDefault(zodType, options, depth); case 'ZodNullable': return this.convertZodNullable(zodType, options, depth); case 'ZodRecord': return this.convertZodRecord(zodType, options, depth); default: this.stats.unsupportedTypes.push(typeName || 'unknown'); logger.warn(`Unsupported Zod type: ${typeName}`, { typeName }); const result = { type: 'string', }; if (options.includeDescriptions) { result.description = `Unsupported type: ${typeName}`; } return result; } } /** * Convert ZodString */ convertZodString(zodString, options) { const property = { type: 'string' }; if (options.includeDescriptions && zodString.description) { property.description = zodString.description; } // Extract string constraints const checks = zodString._def.checks || []; for (const check of checks) { switch (check.kind) { case 'min': property.minLength = check.value; break; case 'max': property.maxLength = check.value; break; case 'regex': property.pattern = check.regex.source; break; case 'email': property.format = 'email'; break; case 'url': property.format = 'uri'; break; case 'uuid': property.format = 'uuid'; break; } } return property; } /** * Convert ZodNumber */ convertZodNumber(zodNumber, options) { const property = { type: 'number' }; if (options.includeDescriptions && zodNumber.description) { property.description = zodNumber.description; } // Extract number constraints const checks = zodNumber._def.checks || []; for (const check of checks) { switch (check.kind) { case 'min': property.minimum = check.value; break; case 'max': property.maximum = check.value; break; case 'int': property.type = 'integer'; break; } } return property; } /** * Convert ZodBoolean */ convertZodBoolean(zodBoolean, options) { const property = { type: 'boolean' }; if (options.includeDescriptions && zodBoolean.description) { property.description = zodBoolean.description; } return property; } /** * Convert ZodArray */ convertZodArray(zodArray, options, depth) { const property = { type: 'array', items: this.convertZodTypeInternal(zodArray.element, options, depth + 1), }; if (options.includeDescriptions && zodArray.description) { property.description = zodArray.description; } return property; } /** * Convert ZodObject to property */ convertZodObjectToProperty(zodObject, options, depth) { const objectSchema = this.convertZodObject(zodObject, options, depth); const result = { type: 'object', properties: objectSchema.properties, }; if (objectSchema.required) { result.required = objectSchema.required; } return result; } /** * Convert ZodEnum */ convertZodEnum(zodEnum, options) { const property = { type: 'string', enum: zodEnum.options, }; if (options.includeDescriptions && zodEnum.description) { property.description = zodEnum.description; } return property; } /** * Convert ZodNativeEnum */ convertZodNativeEnum(zodNativeEnum, options) { const enumValues = Object.values(zodNativeEnum.enum); const property = { type: 'string', enum: enumValues, }; if (options.includeDescriptions && zodNativeEnum.description) { property.description = zodNativeEnum.description; } return property; } /** * Convert ZodLiteral */ convertZodLiteral(zodLiteral, options) { const value = zodLiteral.value; const property = { type: typeof value, enum: [value], }; if (options.includeDescriptions && zodLiteral.description) { property.description = zodLiteral.description; } return property; } /** * Convert ZodUnion (simplified - takes first option) */ convertZodUnion(zodUnion, options, depth) { // For simplicity, convert the first option // In a more sophisticated implementation, this could create a oneOf schema const firstOption = zodUnion.options[0]; const property = this.convertZodTypeInternal(firstOption, options, depth + 1); if (options.includeDescriptions) { property.description = `Union type (simplified to first option)${zodUnion.description ? ': ' + zodUnion.description : ''}`; } return property; } /** * Convert ZodOptional */ convertZodOptional(zodOptional, options, depth) { return this.convertZodTypeInternal(zodOptional.unwrap(), options, depth); } /** * Convert ZodDefault */ convertZodDefault(zodDefault, options, depth) { const property = this.convertZodTypeInternal(zodDefault._def.innerType, options, depth); if (options.includeDefaults) { property.default = zodDefault._def.defaultValue(); } return property; } /** * Convert ZodNullable */ convertZodNullable(zodNullable, options, depth) { const property = this.convertZodTypeInternal(zodNullable.unwrap(), options, depth); if (options.includeDescriptions) { property.description = `Nullable: ${property.description || 'No description'}`; } return property; } /** * Convert ZodRecord */ convertZodRecord(zodRecord, options, _depth) { const description = options.includeDescriptions ? `Record type${zodRecord.description ? ': ' + zodRecord.description : ''}` : undefined; return { type: 'object', ...(description && { description }), }; } /** * Check if a Zod type represents a required property */ isRequiredProperty(zodType) { const typeName = zodType._def?.typeName; return typeName !== 'ZodOptional' && typeName !== 'ZodDefault'; } /** * Generate cache key for Zod type */ getCacheKey(zodType) { try { // Simple cache key based on type name and basic properties const typeName = zodType._def?.typeName || 'unknown'; const description = zodType.description || ''; return `${typeName}:${description}`; } catch { return 'unknown'; } } /** * Reset conversion statistics */ resetStats() { this.stats = { totalProperties: 0, successfulConversions: 0, failedConversions: 0, unsupportedTypes: [], conversionTime: 0, }; } } //# sourceMappingURL=SchemaConverter.js.map