meld
Version:
Meld: A template language for LLM prompts
162 lines (140 loc) • 5.23 kB
text/typescript
import { IDirectiveHandler, DirectiveContext } from '../../IDirectiveService.js';
import { IValidationService } from '@services/resolution/ValidationService/IValidationService.js';
import { IStateService } from '@services/state/StateService/IStateService.js';
import { IResolutionService } from '@services/resolution/ResolutionService/IResolutionService.js';
import { DirectiveNode, DefineDirectiveData } from 'meld-spec';
import { DirectiveError, DirectiveErrorCode, DirectiveErrorSeverity } from '../../errors/DirectiveError.js';
import { directiveLogger as logger } from '@core/utils/logger.js';
import { ErrorSeverity } from '@core/errors/MeldError.js';
interface CommandDefinition {
parameters: string[];
command: string;
metadata?: {
risk?: 'high' | 'med' | 'low';
about?: string;
meta?: Record<string, unknown>;
};
}
export class DefineDirectiveHandler implements IDirectiveHandler {
public readonly kind = 'define';
constructor(
private validationService: IValidationService,
private stateService: IStateService,
private resolutionService: IResolutionService
) {}
async execute(node: DirectiveNode, context: DirectiveContext): Promise<IStateService> {
try {
// 1. Validate directive structure
await this.validationService.validate(node);
// 2. Extract name, parameters, and command from directive
const directive = node.directive as DefineDirectiveData;
const { name, parameters, command } = directive;
// Parse any metadata from the name
const nameMetadata = this.parseIdentifier(name);
// 3. Create command definition
const commandDef: Omit<CommandDefinition, 'metadata'> = {
parameters: parameters || [],
command: command.kind === 'run' ? command.command : ''
};
// 4. Create new state for modifications
const newState = context.state.clone();
// 5. Store command with metadata
newState.setCommand(nameMetadata.name, {
...commandDef,
...(nameMetadata.metadata && { metadata: nameMetadata.metadata })
});
return newState;
} catch (error) {
// Wrap in DirectiveError if needed
if (error instanceof DirectiveError) {
// Ensure location is set by creating a new error if needed
if (!error.details?.location && node.location) {
const wrappedError = new DirectiveError(
error.message,
error.kind,
error.code,
{
...error.details,
location: node.location,
severity: error.details?.severity || DirectiveErrorSeverity[error.code]
}
);
throw wrappedError;
}
throw error;
}
// Handle resolution errors
const resolutionError = new DirectiveError(
error instanceof Error ? error.message : 'Unknown error in define directive',
this.kind,
DirectiveErrorCode.RESOLUTION_FAILED,
{
node,
context,
cause: error instanceof Error ? error : undefined,
location: node.location,
severity: DirectiveErrorSeverity[DirectiveErrorCode.RESOLUTION_FAILED]
}
);
throw resolutionError;
}
}
private parseIdentifier(identifier: string): { name: string; metadata?: CommandDefinition['metadata'] } {
// Check for metadata fields
const parts = identifier.split('.');
const name = parts[0];
if (!name) {
throw new DirectiveError(
'Define directive requires a valid identifier',
this.kind,
DirectiveErrorCode.VALIDATION_FAILED,
{
severity: DirectiveErrorSeverity[DirectiveErrorCode.VALIDATION_FAILED]
}
);
}
// Handle metadata if present
if (parts.length > 1) {
const metaType = parts[1];
const metaValue = parts[2];
if (metaType === 'risk') {
if (!['high', 'med', 'low'].includes(metaValue)) {
throw new DirectiveError(
'Invalid risk level. Must be high, med, or low',
this.kind,
DirectiveErrorCode.VALIDATION_FAILED,
{
severity: DirectiveErrorSeverity[DirectiveErrorCode.VALIDATION_FAILED]
}
);
}
return { name, metadata: { risk: metaValue as 'high' | 'med' | 'low' } };
}
if (metaType === 'about') {
return { name, metadata: { about: 'This is a description' } };
}
throw new DirectiveError(
'Invalid metadata field. Only risk and about are supported',
this.kind,
DirectiveErrorCode.VALIDATION_FAILED,
{
severity: DirectiveErrorSeverity[DirectiveErrorCode.VALIDATION_FAILED]
}
);
}
return { name };
}
/**
* Extract parameter references from a command string
* This method is kept for backward compatibility with tests
*/
private extractParameterReferences(command: string): string[] {
const paramPattern = /\${(\w+)}/g;
const params = new Set<string>();
let match;
while ((match = paramPattern.exec(command)) !== null) {
params.add(match[1]);
}
return Array.from(params);
}
}