UNPKG

meld

Version:

Meld: A template language for LLM prompts

182 lines (166 loc) 6.22 kB
import { DirectiveNode, DirectiveData } from 'meld-spec'; // Define interfaces matching the meld-ast structure for data directives interface DataDirective extends DirectiveData { kind: 'data'; identifier: string; source: 'literal' | 'reference'; value: any; } import { IDirectiveHandler, DirectiveContext } from '@services/pipeline/DirectiveService/IDirectiveService.js'; import { IValidationService } from '@services/resolution/ValidationService/IValidationService.js'; import { IStateService } from '@services/state/StateService/IStateService.js'; import { IResolutionService, ResolutionContext } from '@services/resolution/ResolutionService/IResolutionService.js'; import { ResolutionContextFactory } from '@services/resolution/ResolutionService/ResolutionContextFactory.js'; import { directiveLogger as logger } from '@core/utils/logger.js'; import { DirectiveError, DirectiveErrorCode, DirectiveErrorSeverity } from '@services/pipeline/DirectiveService/errors/DirectiveError.js'; import { ErrorSeverity } from '@core/errors/MeldError.js'; /** * Handler for @data directives * Stores data values in state after resolving variables and processing embedded content */ export class DataDirectiveHandler implements IDirectiveHandler { readonly kind = 'data'; constructor( private validationService: IValidationService, private stateService: IStateService, private resolutionService: IResolutionService ) {} public async execute(node: DirectiveNode, context: DirectiveContext): Promise<IStateService> { logger.debug('Processing data directive', { location: node.location, directive: node.directive }); await this.validationService.validate(node); const { identifier, value, source } = node.directive as DataDirective; const resolutionContext: ResolutionContext = { allowedVariableTypes: { text: true, data: true, path: true, command: true }, currentFilePath: context.currentFilePath, state: context.state }; try { let resolvedValue: unknown; // Values already come parsed from the AST - we just need to resolve any variables inside them if (source === 'literal') { // Value is already parsed by the AST, just resolve any variables it might contain resolvedValue = await this.resolveObjectFields(value, resolutionContext); } else if (source === 'reference') { // Handle reference source (if needed) // This handles cases where value is a reference to another variable resolvedValue = await this.resolutionService.resolveInContext(value, resolutionContext); } else { // Fallback for backward compatibility if (typeof value === 'string') { // Resolve any variables in the string const resolvedJsonString = await this.resolutionService.resolveInContext(value, resolutionContext); try { resolvedValue = JSON.parse(resolvedJsonString); resolvedValue = await this.resolveObjectFields(resolvedValue, resolutionContext); } catch (error) { if (error instanceof Error) { throw new DirectiveError( `Invalid JSON in data directive: ${error.message}`, 'data', DirectiveErrorCode.VALIDATION_FAILED, { node, context, severity: DirectiveErrorSeverity[DirectiveErrorCode.VALIDATION_FAILED] } ); } throw error; } } else { // Value is already an object, resolve variables in it resolvedValue = await this.resolveObjectFields(value, resolutionContext); } } // Store the resolved value in a new state const newState = context.state.clone(); newState.setDataVar(identifier, resolvedValue); return newState; } catch (error) { if (error instanceof Error) { throw new DirectiveError( `Error processing data directive: ${error.message}`, 'data', DirectiveErrorCode.EXECUTION_FAILED, { node, context, severity: DirectiveErrorSeverity[DirectiveErrorCode.EXECUTION_FAILED] } ); } throw error; } } /** * Recursively resolve variables in object fields */ private async resolveObjectFields( obj: any, context: ResolutionContext ): Promise<any> { if (obj === null || obj === undefined) { return obj; } if (typeof obj === 'string') { // If the string contains any variable references, resolve them if (obj.includes('{{') || obj.includes('${') || obj.includes('$')) { return this.resolutionService.resolveInContext(obj, context); } return obj; } if (Array.isArray(obj)) { return Promise.all( obj.map(item => this.resolveObjectFields(item, context)) ); } if (typeof obj === 'object') { const resolved: Record<string, any> = {}; for (const [key, value] of Object.entries(obj)) { // Keep original key, only resolve value resolved[key] = await this.resolveObjectFields(value, context); } return resolved; } // For other primitive types (number, boolean, etc), return as is return obj; } /** * Validate resolved value against schema */ private async validateSchema( value: any, schema: string, node: DirectiveNode ): Promise<void> { try { // TODO: Implement schema validation once schema system is defined // For now, just log that we would validate logger.debug('Schema validation requested', { schema, location: node.location }); } catch (error) { if (error instanceof Error) { throw new DirectiveError( `Schema validation failed: ${error.message}`, 'data', DirectiveErrorCode.VALIDATION_FAILED, { node, severity: DirectiveErrorSeverity[DirectiveErrorCode.VALIDATION_FAILED] } ); } throw error; } } }