brainy/.recovery-workspace/dist-backup-20250910-141917/augmentations/discovery.js

441 lines
16 KiB
JavaScript
Raw Normal View History

/**
* Augmentation Discovery API
*
* Provides discovery and configuration capabilities for augmentations
* Enables tools like brain-cloud to dynamically discover, configure, and manage augmentations
*/
/**
* Augmentation Discovery API
*
* Provides a unified interface for discovering and managing augmentations
*/
export class AugmentationDiscovery {
constructor(registry) {
this.registry = registry;
}
/**
* Discover all registered augmentations with manifests
* @param options Discovery options
* @returns List of augmentation listings
*/
async discover(options = {}) {
const augmentations = this.registry.getAll();
const listings = [];
for (const aug of augmentations) {
// Check if augmentation has manifest support
const hasManifest = 'getManifest' in aug && typeof aug.getManifest === 'function';
if (!hasManifest) {
// Skip augmentations without manifest support (legacy)
continue;
}
try {
// Check if augmentation has manifest method
if (!('getManifest' in aug) || typeof aug.getManifest !== 'function') {
continue;
}
const getManifestFn = aug.getManifest;
const manifest = getManifestFn();
// Apply filters
if (options.category && manifest.category !== options.category) {
continue;
}
if (options.enabled !== undefined) {
const isEnabled = aug.enabled !== false;
if (isEnabled !== options.enabled) {
continue;
}
}
// Build listing
const listing = {
id: manifest.id,
name: manifest.name,
manifest,
status: {
enabled: aug.enabled !== false,
initialized: aug.isInitialized || false,
category: aug.category || manifest.category,
priority: aug.priority
}
};
// Include configuration if requested
if (options.includeConfig && 'getConfig' in aug) {
const getConfigFn = aug.getConfig;
listing.config = {
current: getConfigFn()
};
if (options.includeSchema) {
listing.config.schema = manifest.configSchema;
}
}
listings.push(listing);
}
catch (error) {
console.warn(`Failed to get manifest for augmentation ${aug.name}:`, error);
}
}
// Sort by priority (highest first) then by name
listings.sort((a, b) => {
const priorityDiff = b.status.priority - a.status.priority;
if (priorityDiff !== 0)
return priorityDiff;
return a.name.localeCompare(b.name);
});
return listings;
}
/**
* Get a specific augmentation's manifest
* @param augId Augmentation ID
* @returns Augmentation manifest or null if not found
*/
async getManifest(augId) {
const aug = this.registry.get(augId);
if (!aug || !('getManifest' in aug)) {
return null;
}
try {
const getManifestFn = aug.getManifest;
return getManifestFn();
}
catch (error) {
console.error(`Failed to get manifest for ${augId}:`, error);
return null;
}
}
/**
* Get configuration schema for an augmentation
* @param augId Augmentation ID
* @returns Configuration schema or null
*/
async getConfigSchema(augId) {
const manifest = await this.getManifest(augId);
return manifest?.configSchema || null;
}
/**
* Get current configuration for an augmentation
* @param augId Augmentation ID
* @returns Current configuration or null
*/
async getConfig(augId) {
const aug = this.registry.get(augId);
if (!aug || !('getConfig' in aug)) {
return null;
}
try {
const getConfigFn = aug.getConfig;
return getConfigFn();
}
catch (error) {
console.error(`Failed to get config for ${augId}:`, error);
return null;
}
}
/**
* Update configuration for an augmentation
* @param augId Augmentation ID
* @param config New configuration
* @returns Updated configuration or null on failure
*/
async updateConfig(augId, config) {
const aug = this.registry.get(augId);
if (!aug || !('updateConfig' in aug) || !('getConfig' in aug)) {
throw new Error(`Augmentation ${augId} does not support configuration updates`);
}
try {
const updateConfigFn = aug.updateConfig;
await updateConfigFn(config);
const getConfigFn = aug.getConfig;
return getConfigFn();
}
catch (error) {
throw new Error(`Failed to update config for ${augId}: ${error}`);
}
}
/**
* Validate configuration against schema
* @param augId Augmentation ID
* @param config Configuration to validate
* @returns Validation result
*/
async validateConfig(augId, config) {
const schema = await this.getConfigSchema(augId);
if (!schema) {
return {
valid: true,
warnings: ['No schema available for validation']
};
}
const errors = [];
const warnings = [];
const suggestions = [];
// Check required fields
if (schema.required) {
for (const field of schema.required) {
if (config[field] === undefined) {
errors.push(`Missing required field: ${field}`);
}
}
}
// Validate properties
if (schema.properties) {
for (const [key, propSchema] of Object.entries(schema.properties)) {
const value = config[key];
if (value === undefined) {
// Check if there's a default
if (propSchema.default !== undefined) {
suggestions.push(`Field '${key}' not provided, will use default: ${JSON.stringify(propSchema.default)}`);
}
continue;
}
// Type validation
if (propSchema.type) {
const actualType = Array.isArray(value) ? 'array' : typeof value;
if (actualType !== propSchema.type) {
errors.push(`${key}: expected ${propSchema.type}, got ${actualType}`);
}
}
// Additional validations for specific types
this.validatePropertyValue(key, value, propSchema, errors, warnings);
}
}
// Check for unknown properties
if (schema.additionalProperties === false && schema.properties) {
const allowedKeys = Object.keys(schema.properties);
for (const key of Object.keys(config)) {
if (!allowedKeys.includes(key)) {
warnings.push(`Unknown property: ${key}`);
}
}
}
return {
valid: errors.length === 0,
errors: errors.length > 0 ? errors : undefined,
warnings: warnings.length > 0 ? warnings : undefined,
suggestions: suggestions.length > 0 ? suggestions : undefined
};
}
/**
* Validate a property value against its schema
*/
validatePropertyValue(key, value, schema, errors, warnings) {
// Number validations
if (schema.type === 'number') {
if (schema.minimum !== undefined && value < schema.minimum) {
errors.push(`${key}: value ${value} is less than minimum ${schema.minimum}`);
}
if (schema.maximum !== undefined && value > schema.maximum) {
errors.push(`${key}: value ${value} is greater than maximum ${schema.maximum}`);
}
}
// String validations
if (schema.type === 'string') {
if (schema.minLength !== undefined && value.length < schema.minLength) {
errors.push(`${key}: length ${value.length} is less than minimum ${schema.minLength}`);
}
if (schema.maxLength !== undefined && value.length > schema.maxLength) {
errors.push(`${key}: length ${value.length} is greater than maximum ${schema.maxLength}`);
}
if (schema.pattern) {
const regex = new RegExp(schema.pattern);
if (!regex.test(value)) {
errors.push(`${key}: value does not match pattern ${schema.pattern}`);
}
}
}
// Enum validation
if (schema.enum && !schema.enum.includes(value)) {
errors.push(`${key}: value '${value}' is not one of allowed values: ${schema.enum.join(', ')}`);
}
}
/**
* Get environment variables for an augmentation
* @param augId Augmentation ID
* @returns Map of environment variable names to descriptions
*/
async getEnvironmentVariables(augId) {
const manifest = await this.getManifest(augId);
if (!manifest?.configSchema?.properties) {
return null;
}
const prefix = `BRAINY_AUG_${augId.toUpperCase()}_`;
const vars = {};
for (const [key, prop] of Object.entries(manifest.configSchema.properties)) {
const envKey = prefix + key.replace(/([A-Z])/g, '_$1').toUpperCase();
vars[envKey] = {
configKey: key,
description: prop.description,
type: prop.type,
default: prop.default,
required: manifest.configSchema.required?.includes(key),
currentValue: typeof process !== 'undefined' ? process.env?.[envKey] : undefined
};
}
return vars;
}
/**
* Get configuration examples for an augmentation
* @param augId Augmentation ID
* @returns Configuration examples or empty array
*/
async getConfigExamples(augId) {
const manifest = await this.getManifest(augId);
return manifest?.configExamples || [];
}
/**
* Check if an augmentation supports configuration
* @param augId Augmentation ID
* @returns True if augmentation supports configuration
*/
async supportsConfiguration(augId) {
const aug = this.registry.get(augId);
return !!(aug && 'getConfig' in aug && 'updateConfig' in aug);
}
/**
* Get augmentations by category
* @param category Category to filter by
* @returns List of augmentations in the category
*/
async getByCategory(category) {
return this.discover({ category });
}
/**
* Get enabled augmentations
* @returns List of enabled augmentations
*/
async getEnabled() {
return this.discover({ enabled: true });
}
/**
* Search augmentations by keyword
* @param query Search query
* @returns Matching augmentations
*/
async search(query) {
const all = await this.discover();
const queryLower = query.toLowerCase();
return all.filter(listing => {
const manifest = listing.manifest;
// Search in various fields
const searchFields = [
manifest.name,
manifest.description,
manifest.longDescription,
...(manifest.keywords || []),
manifest.category
].filter(Boolean).map(s => s.toLowerCase());
return searchFields.some(field => field.includes(queryLower));
});
}
/**
* Export configuration for all augmentations
* @returns Map of augmentation IDs to configurations
*/
async exportConfigurations() {
const configs = {};
const listings = await this.discover({ includeConfig: true });
for (const listing of listings) {
if (listing.config?.current) {
configs[listing.id] = listing.config.current;
}
}
return configs;
}
/**
* Import configurations for multiple augmentations
* @param configs Map of augmentation IDs to configurations
* @returns Results of import operation
*/
async importConfigurations(configs) {
const results = {};
for (const [augId, config] of Object.entries(configs)) {
try {
// Validate before applying
const validation = await this.validateConfig(augId, config);
if (!validation.valid) {
results[augId] = {
success: false,
error: `Validation failed: ${validation.errors?.join(', ')}`
};
continue;
}
// Apply configuration
await this.updateConfig(augId, config);
results[augId] = { success: true };
}
catch (error) {
results[augId] = {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
return results;
}
/**
* Generate configuration documentation
* @param augId Augmentation ID
* @returns Markdown documentation
*/
async generateConfigDocs(augId) {
const manifest = await this.getManifest(augId);
if (!manifest)
return null;
const schema = manifest.configSchema;
const examples = manifest.configExamples || [];
const envVars = await this.getEnvironmentVariables(augId);
let docs = `# ${manifest.name} Configuration\n\n`;
docs += `${manifest.description}\n\n`;
if (manifest.longDescription) {
docs += `## Overview\n\n${manifest.longDescription}\n\n`;
}
// Configuration options
if (schema?.properties) {
docs += `## Configuration Options\n\n`;
for (const [key, prop] of Object.entries(schema.properties)) {
const required = schema.required?.includes(key) ? ' *(required)*' : '';
docs += `### \`${key}\`${required}\n\n`;
if (prop.description) {
docs += `${prop.description}\n\n`;
}
docs += `- **Type**: ${prop.type}\n`;
if (prop.default !== undefined) {
docs += `- **Default**: \`${JSON.stringify(prop.default)}\`\n`;
}
if (prop.minimum !== undefined) {
docs += `- **Minimum**: ${prop.minimum}\n`;
}
if (prop.maximum !== undefined) {
docs += `- **Maximum**: ${prop.maximum}\n`;
}
if (prop.enum) {
docs += `- **Allowed values**: ${prop.enum.map(v => `\`${v}\``).join(', ')}\n`;
}
docs += '\n';
}
}
// Environment variables
if (envVars && Object.keys(envVars).length > 0) {
docs += `## Environment Variables\n\n`;
docs += `| Variable | Config Key | Type | Required | Default |\n`;
docs += `|----------|------------|------|----------|----------|\n`;
for (const [envKey, info] of Object.entries(envVars)) {
docs += `| \`${envKey}\` | ${info.configKey} | ${info.type} | ${info.required ? 'Yes' : 'No'} | ${info.default !== undefined ? `\`${info.default}\`` : '-'} |\n`;
}
docs += '\n';
}
// Examples
if (examples.length > 0) {
docs += `## Examples\n\n`;
for (const example of examples) {
docs += `### ${example.name}\n\n`;
if (example.description) {
docs += `${example.description}\n\n`;
}
docs += '```json\n';
docs += JSON.stringify(example.config, null, 2);
docs += '\n```\n\n';
}
}
return docs;
}
}
//# sourceMappingURL=discovery.js.map