@astreus-ai/astreus
Version:
AI Agent Framework with Chat Management
352 lines (301 loc) • 10.7 kB
text/typescript
import { PluginConfig, PluginInstance, Plugin } from "./types";
import { logger } from "./utils";
import { validateRequiredParam, validateRequiredParams } from "./utils/validation";
/**
* Comprehensive Plugin Manager that handles all plugin-related functionality
* This class provides both instance-level management for specific agents
* and static global registry for framework-wide plugin management.
*/
export class PluginManager implements PluginInstance {
public config: PluginConfig;
private tools: Map<string, Plugin>;
// Global registry shared across all instances
private static registry = new Map<string, Plugin>();
constructor(config: PluginConfig) {
// Validate required parameters
validateRequiredParam(config, "config", "PluginManager constructor");
validateRequiredParams(
config,
["name", "tools"],
"PluginManager constructor"
);
// Apply defaults for optional fields
this.config = {
...config,
description: config.description || `Plugin manager for ${config.name}`,
version: config.version || '1.0.0',
tools: config.tools || []
};
this.tools = new Map();
// Initialize tools
if (this.config.tools) {
this.config.tools.forEach((tool) => {
this.registerTool(tool);
});
}
logger.info(`Plugin manager initialized with ${this.tools.size} tools`);
}
// ========== Instance methods for managing local tools ==========
/**
* Get all tools registered with this instance
* @returns Array of all registered tools
*/
getTools(): Plugin[] {
return Array.from(this.tools.values());
}
/**
* Get a specific tool by name
* @param name Name of the tool to retrieve
* @returns Tool if found, undefined otherwise
*/
getTool(name: string): Plugin | undefined {
// Validate required parameters
validateRequiredParam(name, "name", "getTool");
return this.tools.get(name);
}
/**
* Register a tool with this instance
* Also registers the tool with the global registry
* @param tool Tool to register
* @throws Error if the tool is invalid
*/
registerTool(tool: Plugin): void {
// Validate required parameters
validateRequiredParam(tool, "tool", "registerTool");
try {
// Check that tool has a name and execute method
if (!tool.name) {
logger.warn("Cannot register tool: Missing name property");
return;
}
if (!tool.execute || typeof tool.execute !== 'function') {
logger.warn(`Cannot register tool "${tool.name}": Missing execute method`);
return;
}
// Register with the instance
this.tools.set(tool.name, tool);
// Also register with the global registry
PluginManager.register(tool);
logger.debug(`Tool "${tool.name}" registered successfully`);
} catch (error) {
logger.error(`Failed to register tool: ${error}`);
throw error;
}
}
/**
* Remove a tool from this instance
* Also removes the tool from the global registry
* @param name Name of the tool to remove
* @returns true if tool was found and removed, false otherwise
*/
removeTool(name: string): boolean {
// Validate required parameters
validateRequiredParam(name, "name", "removeTool");
const removed = this.tools.delete(name);
// Also remove from global registry if it was removed from instance
if (removed) {
PluginManager.unregister(name);
logger.debug(`Tool "${name}" removed`);
}
return removed;
}
/**
* Check if a tool is registered with this instance
* @param name Name of the tool to check
* @returns true if tool is registered, false otherwise
*/
hasTool(name: string): boolean {
// Validate required parameters
validateRequiredParam(name, "name", "hasTool");
return this.tools.has(name);
}
/**
* Get the number of tools registered with this instance
* @returns Number of tools
*/
getToolCount(): number {
return this.tools.size;
}
// ========== Static methods for global plugin registry ==========
/**
* Register a plugin with the global registry
* @param plugin Plugin to register
* @throws Error if the plugin is invalid
*/
static register(plugin: Plugin): void {
// Validate required parameters
validateRequiredParam(plugin, "plugin", "PluginManager.register");
try {
// Check that plugin has a name and execute method
if (!plugin.name) {
logger.warn("Cannot register plugin: Missing name property");
return;
}
if (!plugin.execute || typeof plugin.execute !== 'function') {
logger.warn(`Cannot register plugin "${plugin.name}": Missing execute method`);
return;
}
this.registry.set(plugin.name, plugin);
logger.debug(`Plugin "${plugin.name}" registered in global registry`);
} catch (error) {
logger.error(`Failed to register plugin in global registry: ${error}`);
throw error;
}
}
/**
* Unregister a plugin from the global registry
* @param name Name of the plugin to unregister
* @returns true if plugin was found and removed, false otherwise
*/
static unregister(name: string): boolean {
// Validate required parameters
validateRequiredParam(name, "name", "PluginManager.unregister");
const result = this.registry.delete(name);
if (result) {
logger.debug(`Plugin "${name}" unregistered from global registry`);
}
return result;
}
/**
* Get a plugin by name from the global registry
* @param name Name of the plugin to retrieve
* @returns Plugin if found, undefined otherwise
*/
static get(name: string): Plugin | undefined {
// Validate required parameters
validateRequiredParam(name, "name", "PluginManager.get");
return this.registry.get(name);
}
/**
* Get all plugins from the global registry
* @returns Array of all registered plugins
*/
static getAll(): Plugin[] {
return Array.from(this.registry.values());
}
/**
* Get all plugins for a specific agent
* @param agentId ID of the agent to get plugins for
* @returns Array of plugins for the specified agent
*/
static getByAgent(agentId: string): Plugin[] {
try {
// Validate required parameters
validateRequiredParam(agentId, "agentId", "getByAgent");
// Log what we're doing
logger.debug(`Getting plugins for agent ${agentId}`);
logger.debug(`Total plugins in registry: ${this.registry.size}`);
// Do safe access of the registry contents
const plugins = Array.from(this.registry.values());
// Log each plugin for debugging
plugins.forEach((plugin, index) => {
const name = plugin?.name || 'unnamed';
logger.debug(`Plugin ${index}: ${name}`);
});
// For now, return all plugins as we don't track agent-plugin relationships
// In the future, this could be enhanced to track which plugins are used by which agents
return plugins;
} catch (error) {
// Log the error and return empty array
logger.error(`Error getting plugins for agent ${agentId}:`, error);
return [];
}
}
/**
* Reset the global plugin registry
* Useful for testing or when reloading plugins
*/
static reset(): void {
this.registry.clear();
logger.info("Global plugin registry reset");
}
/**
* Check if a plugin exists in the global registry
* @param name Name of the plugin to check
* @returns true if plugin exists, false otherwise
*/
static has(name: string): boolean {
// Validate required parameters
validateRequiredParam(name, "name", "PluginManager.has");
return this.registry.has(name);
}
/**
* Get the number of plugins in the global registry
* @returns Number of plugins
*/
static count(): number {
return this.registry.size;
}
/**
* Load a plugin from a repository or object
* @param pluginOrPath Plugin object or path to a plugin module
* @returns Promise that resolves when the plugin is loaded and registered
* @throws Error if loading fails
*/
static async load(pluginOrPath: string | Plugin): Promise<void> {
// Validate required parameters
validateRequiredParam(pluginOrPath, "pluginOrPath", "PluginManager.load");
try {
if (typeof pluginOrPath === "string") {
// This is a path to a plugin module
try {
// Dynamic import
const module = await import(pluginOrPath);
const plugin = module.default || module;
if (plugin && typeof plugin === 'object') {
this.register(plugin);
logger.info(`Plugin loaded from path: ${pluginOrPath}`);
} else {
const error = `Plugin module at ${pluginOrPath} does not export a valid plugin`;
logger.warn(error);
throw new Error(error);
}
} catch (error) {
logger.error(`Failed to load plugin from ${pluginOrPath}:`, error);
throw error;
}
} else if (pluginOrPath && typeof pluginOrPath === 'object') {
// This is a plugin object
this.register(pluginOrPath);
logger.info(`Plugin "${pluginOrPath.name}" loaded directly`);
} else {
throw new Error('Invalid plugin format');
}
} catch (error) {
logger.error("Error loading plugin:", error);
throw error;
}
}
/**
* Load multiple plugins at once
* @param plugins Array of plugin objects or paths
* @returns Promise that resolves when all plugins are loaded
* @throws Error if loading any plugin fails
*/
static async loadMany(plugins: Array<string | Plugin>): Promise<void> {
// Validate required parameters
validateRequiredParam(plugins, "plugins", "PluginManager.loadMany");
try {
await Promise.all(plugins.map((plugin) => this.load(plugin)));
logger.info(`Loaded ${plugins.length} plugins`);
} catch (error) {
logger.error("Error loading multiple plugins:", error);
throw error;
}
}
/**
* Create a new PluginManager instance
* @param config Configuration for the plugin manager
* @returns New PluginManager instance
*/
static create(config: PluginConfig): PluginInstance {
// Validate required parameters
validateRequiredParam(config, "config", "PluginManager.create");
validateRequiredParams(
config,
["name", "tools"],
"PluginManager.create"
);
return new PluginManager(config);
}
}