tasq/node_modules/agentic-flow/dist/sdk/plugins.js

279 lines
8.2 KiB
JavaScript

/**
* Plugins System - Load and manage Claude Agent SDK plugins
*
* Supports loading plugins from:
* - Local filesystem
* - NPM packages
* - Remote URLs
* - In-memory definitions
*/
import { logger } from "../utils/logger.js";
import { existsSync, readFileSync } from "fs";
import { join, resolve } from "path";
// Plugin registry
const loadedPlugins = new Map();
/**
* Load a plugin from configuration
*/
export async function loadPlugin(config) {
try {
switch (config.type) {
case 'local':
return await loadLocalPlugin(config);
case 'npm':
return await loadNpmPlugin(config);
case 'remote':
return await loadRemotePlugin(config);
case 'inline':
return loadInlinePlugin(config);
default:
logger.error('Unknown plugin type', { config });
return null;
}
}
catch (error) {
logger.error('Failed to load plugin', { config, error: error.message });
return null;
}
}
/**
* Load plugin from local filesystem
*/
async function loadLocalPlugin(config) {
const pluginPath = resolve(config.path);
if (!existsSync(pluginPath)) {
logger.error('Plugin path does not exist', { path: pluginPath });
return null;
}
// Look for package.json or plugin.json
const packageJsonPath = join(pluginPath, 'package.json');
const pluginJsonPath = join(pluginPath, 'plugin.json');
let metadata = { name: 'unknown', version: '0.0.0' };
if (existsSync(packageJsonPath)) {
metadata = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
}
else if (existsSync(pluginJsonPath)) {
metadata = JSON.parse(readFileSync(pluginJsonPath, 'utf-8'));
}
// Try to load the plugin module
const mainPath = join(pluginPath, metadata.main || 'index.js');
if (!existsSync(mainPath)) {
logger.error('Plugin main file not found', { path: mainPath });
return null;
}
const module = await import(mainPath);
const tools = module.tools || module.default?.tools || [];
const plugin = {
name: metadata.name,
version: metadata.version,
source: `local:${pluginPath}`,
tools,
enabled: true,
loadedAt: Date.now()
};
loadedPlugins.set(plugin.name, plugin);
logger.info('Local plugin loaded', { name: plugin.name, tools: tools.length });
return plugin;
}
/**
* Load plugin from NPM package
*/
async function loadNpmPlugin(config) {
try {
const module = await import(config.package);
const metadata = module.default?.metadata || { name: config.package, version: config.version || '0.0.0' };
const tools = module.tools || module.default?.tools || [];
const plugin = {
name: metadata.name || config.package,
version: metadata.version || config.version || '0.0.0',
source: `npm:${config.package}`,
tools,
enabled: true,
loadedAt: Date.now()
};
loadedPlugins.set(plugin.name, plugin);
logger.info('NPM plugin loaded', { name: plugin.name, tools: tools.length });
return plugin;
}
catch (error) {
logger.error('Failed to load NPM plugin', { package: config.package, error: error.message });
return null;
}
}
/**
* Load plugin from remote URL
*/
async function loadRemotePlugin(config) {
try {
const response = await fetch(config.url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const content = await response.text();
// Verify checksum if provided
if (config.checksum) {
const hash = await computeHash(content);
if (hash !== config.checksum) {
throw new Error('Checksum mismatch - plugin may be compromised');
}
}
// Parse plugin definition (JSON format)
const pluginDef = JSON.parse(content);
const plugin = {
name: pluginDef.name || 'remote-plugin',
version: pluginDef.version || '0.0.0',
source: `remote:${config.url}`,
tools: pluginDef.tools || [],
enabled: true,
loadedAt: Date.now()
};
loadedPlugins.set(plugin.name, plugin);
logger.info('Remote plugin loaded', { name: plugin.name, url: config.url });
return plugin;
}
catch (error) {
logger.error('Failed to load remote plugin', { url: config.url, error: error.message });
return null;
}
}
/**
* Load inline plugin from configuration
*/
function loadInlinePlugin(config) {
const plugin = {
name: config.name,
version: '1.0.0',
source: 'inline',
tools: config.tools,
enabled: true,
loadedAt: Date.now()
};
loadedPlugins.set(plugin.name, plugin);
logger.info('Inline plugin loaded', { name: plugin.name, tools: config.tools.length });
return plugin;
}
/**
* Compute SHA-256 hash for checksum verification
*/
async function computeHash(content) {
const encoder = new TextEncoder();
const data = encoder.encode(content);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
/**
* Get all loaded plugins
*/
export function getLoadedPlugins() {
return Array.from(loadedPlugins.values());
}
/**
* Get plugin by name
*/
export function getPlugin(name) {
return loadedPlugins.get(name) || null;
}
/**
* Enable/disable a plugin
*/
export function setPluginEnabled(name, enabled) {
const plugin = loadedPlugins.get(name);
if (!plugin)
return false;
plugin.enabled = enabled;
logger.info('Plugin state changed', { name, enabled });
return true;
}
/**
* Unload a plugin
*/
export function unloadPlugin(name) {
const existed = loadedPlugins.delete(name);
if (existed) {
logger.info('Plugin unloaded', { name });
}
return existed;
}
/**
* Get all tools from enabled plugins
*/
export function getAllPluginTools() {
const tools = [];
for (const plugin of loadedPlugins.values()) {
if (plugin.enabled) {
tools.push(...plugin.tools);
}
}
return tools;
}
/**
* Execute a plugin tool
*/
export async function executePluginTool(toolName, input) {
for (const plugin of loadedPlugins.values()) {
if (!plugin.enabled)
continue;
const tool = plugin.tools.find(t => t.name === toolName);
if (tool) {
logger.info('Executing plugin tool', { plugin: plugin.name, tool: toolName });
return tool.handler(input);
}
}
throw new Error(`Plugin tool not found: ${toolName}`);
}
/**
* Load plugins from SDK configuration
*/
export async function loadPluginsFromConfig(configs) {
const loaded = [];
for (const config of configs) {
const plugin = await loadPlugin(config);
if (plugin) {
loaded.push(plugin);
}
}
logger.info('Plugins loaded from config', { total: loaded.length });
return loaded;
}
/**
* Get plugin configuration for SDK query options
*/
export function getPluginsForSdk() {
const plugins = [];
for (const plugin of loadedPlugins.values()) {
if (!plugin.enabled)
continue;
if (plugin.source.startsWith('local:')) {
plugins.push({
type: 'local',
path: plugin.source.replace('local:', '')
});
}
else if (plugin.source.startsWith('npm:')) {
plugins.push({
type: 'npm',
package: plugin.source.replace('npm:', '')
});
}
}
return plugins;
}
/**
* Create an inline plugin helper
*/
export function createPlugin(name, tools) {
return loadInlinePlugin({ type: 'inline', name, tools });
}
/**
* Plugin tool builder for type-safe tool creation
*/
export function defineTool(config) {
return {
name: config.name,
description: config.description,
inputSchema: config.inputSchema,
handler: config.handler
};
}
//# sourceMappingURL=plugins.js.map