open-brainy/src/pluginLoader.ts
David Snelling b031a40b1c feat: add external plugin loader and examples for Brainy
Introduced `pluginLoader.ts` to enable loading and configuring augmentation plugins from external packages. Added `examples/externalPlugins.js` to demonstrate usage. Enhanced storage status reporting in `opfsStorage` and `BrainyData` with detailed storage capacity and usage methods. Updated README with an external plugin usage guide.
2025-05-28 15:39:14 -07:00

241 lines
6.6 KiB
TypeScript

/**
* Plugin Loader for Brainy Augmentations
*
* This module provides functionality for loading and configuring augmentation plugins
* from external npm packages. It allows consumers of the library to easily integrate
* externally installed augmentation plugins into the Brainy system.
*/
import { AugmentationPipeline, augmentationPipeline, ExecutionMode } from './augmentationPipeline.js';
import { AugmentationType, IAugmentation, ISenseAugmentation, IConduitAugmentation } from './types/augmentations.js';
/**
* Configuration options for loading plugins
*/
export interface PluginLoaderOptions {
/**
* Whether to use the default augmentation pipeline instance
* If false, a new pipeline instance will be created
* @default true
*/
useDefaultPipeline?: boolean;
/**
* Whether to initialize the augmentations after loading
* @default true
*/
initializeAfterLoading?: boolean;
/**
* Order of augmentation types to load
* By default, sense augmentations are loaded first
* @default [AugmentationType.SENSE, AugmentationType.CONDUIT, ...]
*/
augmentationOrder?: AugmentationType[];
}
/**
* Default options for the plugin loader
*/
const DEFAULT_OPTIONS: PluginLoaderOptions = {
useDefaultPipeline: true,
initializeAfterLoading: true,
augmentationOrder: [
AugmentationType.SENSE,
AugmentationType.CONDUIT,
AugmentationType.COGNITION,
AugmentationType.MEMORY,
AugmentationType.PERCEPTION,
AugmentationType.DIALOG,
AugmentationType.ACTIVATION,
AugmentationType.WEBSOCKET
]
};
/**
* Plugin configuration for a specific augmentation
*/
export interface PluginConfig {
/**
* The plugin module name or path
* This can be an npm package name or a relative path
*/
plugin: string;
/**
* Optional configuration to pass to the plugin
*/
config?: Record<string, any>;
/**
* Optional type of the augmentation
* If not provided, it will be determined automatically
*/
type?: AugmentationType;
}
/**
* Result of loading plugins
*/
export interface PluginLoadResult {
/**
* The augmentation pipeline instance
*/
pipeline: AugmentationPipeline;
/**
* Map of loaded augmentations by type
*/
augmentations: Map<AugmentationType, IAugmentation[]>;
/**
* Any errors that occurred during loading
*/
errors: Error[];
}
/**
* Loads augmentation plugins from external npm packages
*
* @param plugins Array of plugin configurations
* @param options Options for loading plugins
* @returns Promise that resolves to the plugin load result
*/
export async function loadPlugins(
plugins: PluginConfig[],
options: PluginLoaderOptions = {}
): Promise<PluginLoadResult> {
// Merge options with defaults
const opts = { ...DEFAULT_OPTIONS, ...options };
// Use the default pipeline or create a new one
const pipeline = opts.useDefaultPipeline ? augmentationPipeline : new AugmentationPipeline();
// Track loaded augmentations by type
const loadedAugmentations = new Map<AugmentationType, IAugmentation[]>();
const errors: Error[] = [];
// Group plugins by type according to the specified order
const pluginsByType = new Map<AugmentationType, PluginConfig[]>();
// Initialize the map with empty arrays for each type
for (const type of opts.augmentationOrder!) {
pluginsByType.set(type, []);
loadedAugmentations.set(type, []);
}
// Group plugins by type
for (const pluginConfig of plugins) {
const type = pluginConfig.type || AugmentationType.SENSE; // Default to SENSE if not specified
const typePlugins = pluginsByType.get(type) || [];
typePlugins.push(pluginConfig);
pluginsByType.set(type, typePlugins);
}
// Load plugins in the specified order
for (const type of opts.augmentationOrder!) {
const typePlugins = pluginsByType.get(type) || [];
for (const pluginConfig of typePlugins) {
try {
// Import the plugin module
const pluginModule = await import(pluginConfig.plugin);
// Get the default export or the named export that matches the plugin name
const PluginClass = pluginModule.default || pluginModule[pluginConfig.plugin.split('/').pop()!];
if (!PluginClass) {
throw new Error(`Could not find a valid export in plugin ${pluginConfig.plugin}`);
}
// Instantiate the plugin with the provided configuration
const plugin = new PluginClass(pluginConfig.config);
// Register the plugin with the pipeline
pipeline.register(plugin);
// Add to the loaded augmentations map
const typeAugmentations = loadedAugmentations.get(type) || [];
typeAugmentations.push(plugin);
loadedAugmentations.set(type, typeAugmentations);
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
errors.push(err);
console.error(`Error loading plugin ${pluginConfig.plugin}:`, err);
}
}
}
// Initialize the augmentations if requested
if (opts.initializeAfterLoading) {
try {
await pipeline.initialize();
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
errors.push(err);
console.error('Error initializing augmentations:', err);
}
}
return {
pipeline,
augmentations: loadedAugmentations,
errors
};
}
/**
* Configures and starts the augmentation pipeline with the specified plugins
*
* @param plugins Array of plugin configurations
* @param options Options for loading plugins
* @returns Promise that resolves to the plugin load result
*/
export async function configureAndStartPipeline(
plugins: PluginConfig[],
options: PluginLoaderOptions = {}
): Promise<PluginLoadResult> {
// Load the plugins
const result = await loadPlugins(plugins, {
...options,
initializeAfterLoading: true
});
return result;
}
/**
* Creates a plugin configuration for a sense augmentation
*
* @param plugin The plugin module name or path
* @param config Optional configuration to pass to the plugin
* @returns Plugin configuration
*/
export function createSensePluginConfig(
plugin: string,
config?: Record<string, any>
): PluginConfig {
return {
plugin,
config,
type: AugmentationType.SENSE
};
}
/**
* Creates a plugin configuration for a conduit augmentation
*
* @param plugin The plugin module name or path
* @param config Optional configuration to pass to the plugin
* @returns Plugin configuration
*/
export function createConduitPluginConfig(
plugin: string,
config?: Record<string, any>
): PluginConfig {
return {
plugin,
config,
type: AugmentationType.CONDUIT
};
}