UNPKG

its-compiler-js

Version:

JavaScript/TypeScript implementation of the Instruction Template Specification (ITS) compiler

387 lines 16.7 kB
/** * Main ITS Compiler implementation */ import { promises as fs } from 'fs'; import { URL } from 'url'; import { OverrideType, ITSValidationError, ITSCompilationError, } from './types.js'; import { SecurityValidator, DEFAULT_SECURITY_CONFIG } from './security.js'; import { VariableProcessor } from './variable-processor.js'; import { ConditionalEvaluator } from './conditional-evaluator.js'; import { SchemaLoader } from './schema-loader.js'; export class ITSCompiler { constructor(securityConfig = DEFAULT_SECURITY_CONFIG) { this.securityConfig = securityConfig; this.securityValidator = new SecurityValidator(securityConfig); this.variableProcessor = new VariableProcessor(); this.conditionalEvaluator = new ConditionalEvaluator(securityConfig.maxExpressionLength); this.schemaLoader = new SchemaLoader(3600000, securityConfig, securityConfig.requestTimeout); } /** * Compile a template from file */ async compileFile(templatePath, variables, options) { try { const templateContent = await fs.readFile(templatePath, 'utf-8'); const template = JSON.parse(templateContent); // Set base URL for relative schema references const baseUrl = this.getBaseUrlFromPath(templatePath); return await this.compile(template, variables, { ...options, baseUrl }); } catch (error) { if (error instanceof SyntaxError) { throw new ITSCompilationError(`Invalid JSON in template file: ${error.message}`, undefined, undefined, 'file_parsing'); } throw new ITSCompilationError(`Failed to load template file: ${error}`, undefined, undefined, 'file_loading'); } } /** * Compile a template */ async compile(template, variables, options) { const startTime = Date.now(); const mergedOptions = { ...options }; try { // Security validation this.securityValidator.validateTemplate(template); // Validate template structure const validationResult = await this.validate(template, mergedOptions.baseUrl); if (!validationResult.isValid) { throw new ITSValidationError('Template validation failed', undefined, validationResult.errors, validationResult.securityIssues); } // Merge template variables with provided variables const templateVariables = template.variables || {}; const mergedVariables = { ...templateVariables, ...(variables || {}) }; // Load and resolve instruction types const { instructionTypes, overrides } = await this.loadInstructionTypes(template, mergedOptions.baseUrl); // Process variables in content const processedContent = this.variableProcessor.processContent(template.content, mergedVariables); // Evaluate conditionals const finalContent = this.conditionalEvaluator.evaluateContent(processedContent, mergedVariables); // Generate final prompt const prompt = this.generatePrompt(finalContent, instructionTypes, template); const compilationTime = Date.now() - startTime; return { prompt, template, variables: mergedVariables, overrides, warnings: validationResult.warnings, compilationTime, }; } catch (error) { if (error instanceof ITSValidationError || error instanceof ITSCompilationError) { throw error; } throw new ITSCompilationError(`Compilation failed: ${error}`, undefined, undefined, 'compilation'); } } /** * Validate a template */ async validate(template, baseUrl) { const startTime = Date.now(); const errors = []; const warnings = []; const securityIssues = []; try { // Security validation this.securityValidator.validateTemplate(template); } catch (error) { if (error instanceof Error) { securityIssues.push(error.message); } } // Required fields if (!template.version) { errors.push('Missing required field: version'); } if (!template.content) { errors.push('Missing required field: content'); } else if (!Array.isArray(template.content)) { errors.push("Field 'content' must be an array"); } else if (template.content.length === 0) { errors.push("Field 'content' cannot be empty"); } // Validate content elements if (template.content && Array.isArray(template.content)) { const contentErrors = this.validateContent(template.content); errors.push(...contentErrors); } // Try to load schemas try { await this.loadInstructionTypes(template, baseUrl); } catch (error) { if (error instanceof Error) { errors.push(`Schema loading error: ${error.message}`); } } // Validate variables const templateVariables = template.variables || {}; if (template.content) { const varErrors = this.variableProcessor.validateVariables(template.content, templateVariables); errors.push(...varErrors); } const validationTime = Date.now() - startTime; return { isValid: errors.length === 0 && securityIssues.length === 0, errors, warnings, securityIssues, validationTime, }; } /** * Validate content elements */ validateContent(content) { const errors = []; for (let i = 0; i < content.length; i++) { const element = content[i]; if (!element.type) { errors.push(`Content element ${i} missing required field: type`); continue; } if (element.type === 'text') { const textElement = element; if (!textElement.text) { errors.push(`Text element ${i} missing required field: text`); } } else if (element.type === 'placeholder') { const placeholderElement = element; if (!placeholderElement.instructionType) { errors.push(`Placeholder element ${i} missing required field: instructionType`); } if (!placeholderElement.config) { errors.push(`Placeholder element ${i} missing required field: config`); } else if (typeof placeholderElement.config !== 'object') { errors.push(`Placeholder element ${i} config must be an object`); } else if (!placeholderElement.config.description) { errors.push(`Placeholder element ${i} config missing required field: description`); } } else if (element.type === 'conditional') { const conditionalElement = element; if (!conditionalElement.condition) { errors.push(`Conditional element ${i} missing required field: condition`); } if (!conditionalElement.content) { errors.push(`Conditional element ${i} missing required field: content`); } else if (!Array.isArray(conditionalElement.content)) { errors.push(`Conditional element ${i} content must be an array`); } else { const nestedErrors = this.validateContent(conditionalElement.content); errors.push(...nestedErrors); } if (conditionalElement.else) { if (!Array.isArray(conditionalElement.else)) { errors.push(`Conditional element ${i} else must be an array`); } else { const elseErrors = this.validateContent(conditionalElement.else); errors.push(...elseErrors); } } } else { errors.push(`Content element ${i} has invalid type: ${element.type}`); } } return errors; } /** * Load and resolve instruction types from schemas */ async loadInstructionTypes(template, baseUrl) { const instructionTypes = {}; const overrides = []; // Load extended schemas in order const extends_ = template.extends || []; for (const schemaUrl of extends_) { const schema = await this.schemaLoader.loadSchema(schemaUrl, baseUrl); const schemaTypes = schema.instructionTypes || {}; // Check for overrides for (const [typeName, typeDef] of Object.entries(schemaTypes)) { if (typeName in instructionTypes) { overrides.push({ typeName, overrideSource: schemaUrl, overriddenSource: instructionTypes[typeName].source || 'unknown', overrideType: OverrideType.SCHEMA_EXTENSION, }); } const typeDefAny = typeDef; const instructionTypeDef = { template: typeDefAny.template, source: schemaUrl, }; if (typeDefAny.description !== undefined) { instructionTypeDef.description = typeDefAny.description; } if (typeDefAny.configSchema !== undefined) { instructionTypeDef.configSchema = typeDefAny.configSchema; } instructionTypes[typeName] = instructionTypeDef; } } // Apply custom instruction types (highest precedence) const customTypes = template.customInstructionTypes || {}; for (const [typeName, typeDef] of Object.entries(customTypes)) { if (typeName in instructionTypes) { overrides.push({ typeName, overrideSource: 'customInstructionTypes', overriddenSource: instructionTypes[typeName].source || 'unknown', overrideType: OverrideType.CUSTOM, }); } const customTypeDef = { template: typeDef.template, source: 'custom', }; if (typeDef.description !== undefined) { customTypeDef.description = typeDef.description; } if (typeDef.configSchema !== undefined) { customTypeDef.configSchema = typeDef.configSchema; } instructionTypes[typeName] = customTypeDef; } return { instructionTypes, overrides }; } /** * Generate the final AI prompt */ generatePrompt(content, instructionTypes, template) { // Get compiler configuration const compilerConfig = template.compilerConfig || {}; const systemPrompt = compilerConfig.systemPrompt || this.getDefaultSystemPrompt(); const instructionWrapper = compilerConfig.instructionWrapper || '<<{instruction}>>'; const processingInstructions = compilerConfig.processingInstructions || this.getDefaultProcessingInstructions(); // Process content elements const processedContent = []; for (const element of content) { if (element.type === 'text') { processedContent.push(element.text); } else if (element.type === 'placeholder') { const placeholderElement = element; const instruction = this.generateInstruction(placeholderElement, instructionTypes); // Check if the instruction already has wrapper brackets if (instruction.startsWith('<<') && instruction.endsWith('>>')) { processedContent.push(instruction); } else { const wrappedInstruction = instructionWrapper.replace('{instruction}', instruction); processedContent.push(wrappedInstruction); } } } // Assemble final prompt const promptParts = ['INTRODUCTION', '', systemPrompt, '', 'INSTRUCTIONS', '']; processingInstructions.forEach((instruction, i) => { promptParts.push(`${i + 1}. ${instruction}`); }); promptParts.push('', 'TEMPLATE', '', processedContent.join('')); return promptParts.join('\n'); } /** * Generate an instruction for a placeholder */ generateInstruction(placeholder, instructionTypes) { const instructionTypeName = placeholder.instructionType; const config = placeholder.config; if (!(instructionTypeName in instructionTypes)) { const availableTypes = Object.keys(instructionTypes); throw new ITSCompilationError(`Unknown instruction type: '${instructionTypeName}'`, placeholder.id, instructionTypeName, 'instruction_generation', { availableTypes }); } const instructionType = instructionTypes[instructionTypeName]; try { return this.formatInstruction(instructionType, config); } catch (error) { throw new ITSCompilationError(`Missing required configuration for instruction type '${instructionTypeName}': ${error}`, placeholder.id, instructionTypeName, 'instruction_generation'); } } /** * Format an instruction template with config values */ formatInstruction(instructionType, config) { const description = config.description || ''; // Start with the template let formattedTemplate = instructionType.template; // Replace description placeholder formattedTemplate = formattedTemplate.replace(/\{description\}/g, description); // Replace other config placeholders for (const [key, value] of Object.entries(config)) { if (key !== 'description') { const placeholder = `{${key}}`; formattedTemplate = formattedTemplate.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), String(value)); } } return formattedTemplate; } /** * Get base URL from file path */ getBaseUrlFromPath(filePath) { try { const url = new URL(`file://${filePath}`); const pathParts = url.pathname.split('/'); pathParts.pop(); // Remove filename return url.protocol + '//' + pathParts.join('/') + '/'; } catch { return ''; } } /** * Get default system prompt */ getDefaultSystemPrompt() { return ('You are an AI assistant that fills in content templates. ' + 'Follow the instructions exactly and replace each placeholder with ' + 'appropriate content based on the user prompts provided. ' + 'Respond only with the transformed content.'); } /** * Get default processing instructions */ getDefaultProcessingInstructions() { return [ 'Replace each placeholder marked with << >> with generated content', "The user's content request is wrapped in ([{< >}]) to distinguish it from instructions", 'Follow the format requirements specified after each user prompt', 'Maintain the existing structure and formatting of the template', 'Only replace the placeholders - do not modify any other text', 'Generate content that matches the tone and style requested', 'Respond only with the transformed content - do not include any explanations or additional text', ]; } /** * Clear schema cache */ clearCache() { this.schemaLoader.clearCache(); } /** * Get security status */ getSecurityStatus() { return { securityEnabled: true, config: this.securityConfig, cache: this.schemaLoader.getCacheStats(), }; } } //# sourceMappingURL=compiler.js.map