Files
poimen-workflows/internal/plugins/registry.go
T

323 lines
7.5 KiB
Go
Raw Normal View History

package plugins
import (
"fmt"
"sync"
"time"
)
// SkillPlugin represents a custom skill plugin
type SkillPlugin interface {
// Name returns the plugin name
Name() string
// Version returns the plugin version
Version() string
// Execute executes the plugin with the given input
Execute(input map[string]interface{}) (map[string]interface{}, error)
// Validate validates the plugin configuration
Validate() error
// Description returns a human-readable description
Description() string
}
// PluginMetadata holds metadata about a plugin
type PluginMetadata struct {
Name string `json:"name"`
Version string `json:"version"`
Author string `json:"author"`
Description string `json:"description"`
URL string `json:"url"`
Config map[string]interface{} `json:"config,omitempty"`
LoadedAt time.Time `json:"loaded_at"`
Enabled bool `json:"enabled"`
}
// PluginRegistry manages custom skill plugins
type PluginRegistry struct {
mu sync.RWMutex
plugins map[string]SkillPlugin
metadata map[string]*PluginMetadata
executionLog map[string][]*ExecutionRecord
stats *PluginStats
}
// ExecutionRecord tracks plugin execution
type ExecutionRecord struct {
PluginName string
Timestamp time.Time
Duration time.Duration
Input map[string]interface{}
Output map[string]interface{}
Error error
Success bool
}
// PluginStats tracks plugin statistics
type PluginStats struct {
TotalExecutions int
SuccessfulExecutions int
FailedExecutions int
TotalPlugins int
EnabledPlugins int
AverageExecutionTime time.Duration
}
// NewPluginRegistry creates a new plugin registry
func NewPluginRegistry() *PluginRegistry {
return &PluginRegistry{
plugins: make(map[string]SkillPlugin),
metadata: make(map[string]*PluginMetadata),
executionLog: make(map[string][]*ExecutionRecord),
stats: &PluginStats{
TotalExecutions: 0,
SuccessfulExecutions: 0,
FailedExecutions: 0,
TotalPlugins: 0,
EnabledPlugins: 0,
},
}
}
// Register registers a new plugin
func (pr *PluginRegistry) Register(plugin SkillPlugin, author string, config map[string]interface{}) error {
if plugin == nil {
return fmt.Errorf("plugin cannot be nil")
}
// Validate plugin
if err := plugin.Validate(); err != nil {
return fmt.Errorf("plugin validation failed: %w", err)
}
pr.mu.Lock()
defer pr.mu.Unlock()
name := plugin.Name()
if _, exists := pr.plugins[name]; exists {
return fmt.Errorf("plugin already registered: %s", name)
}
pr.plugins[name] = plugin
pr.metadata[name] = &PluginMetadata{
Name: name,
Version: plugin.Version(),
Author: author,
Description: plugin.Description(),
URL: fmt.Sprintf("plugin://%s", name),
Config: config,
LoadedAt: time.Now(),
Enabled: true,
}
pr.stats.TotalPlugins++
pr.stats.EnabledPlugins++
pr.executionLog[name] = make([]*ExecutionRecord, 0)
return nil
}
// Unregister unregisters a plugin
func (pr *PluginRegistry) Unregister(name string) error {
pr.mu.Lock()
defer pr.mu.Unlock()
if _, exists := pr.plugins[name]; !exists {
return fmt.Errorf("plugin not found: %s", name)
}
delete(pr.plugins, name)
if pr.metadata[name].Enabled {
pr.stats.EnabledPlugins--
}
pr.stats.TotalPlugins--
delete(pr.metadata, name)
return nil
}
// Execute executes a plugin by name
func (pr *PluginRegistry) Execute(name string, input map[string]interface{}) (map[string]interface{}, error) {
pr.mu.RLock()
plugin, exists := pr.plugins[name]
metadata, metaExists := pr.metadata[name]
pr.mu.RUnlock()
if !exists {
return nil, fmt.Errorf("plugin not found: %s", name)
}
if !metaExists || !metadata.Enabled {
return nil, fmt.Errorf("plugin is disabled: %s", name)
}
start := time.Now()
output, err := plugin.Execute(input)
duration := time.Since(start)
// Record execution
record := &ExecutionRecord{
PluginName: name,
Timestamp: start,
Duration: duration,
Input: input,
Output: output,
Error: err,
Success: err == nil,
}
pr.mu.Lock()
pr.executionLog[name] = append(pr.executionLog[name], record)
pr.stats.TotalExecutions++
if err == nil {
pr.stats.SuccessfulExecutions++
} else {
pr.stats.FailedExecutions++
}
pr.mu.Unlock()
return output, err
}
// Get retrieves a plugin by name
func (pr *PluginRegistry) Get(name string) (SkillPlugin, bool) {
pr.mu.RLock()
defer pr.mu.RUnlock()
plugin, exists := pr.plugins[name]
return plugin, exists
}
// GetMetadata retrieves plugin metadata
func (pr *PluginRegistry) GetMetadata(name string) (*PluginMetadata, bool) {
pr.mu.RLock()
defer pr.mu.RUnlock()
meta, exists := pr.metadata[name]
return meta, exists
}
// ListPlugins returns all registered plugins
func (pr *PluginRegistry) ListPlugins() map[string]*PluginMetadata {
pr.mu.RLock()
defer pr.mu.RUnlock()
result := make(map[string]*PluginMetadata)
for name, meta := range pr.metadata {
result[name] = meta
}
return result
}
// EnablePlugin enables a plugin
func (pr *PluginRegistry) EnablePlugin(name string) error {
pr.mu.Lock()
defer pr.mu.Unlock()
meta, exists := pr.metadata[name]
if !exists {
return fmt.Errorf("plugin not found: %s", name)
}
if meta.Enabled {
return fmt.Errorf("plugin already enabled: %s", name)
}
meta.Enabled = true
pr.stats.EnabledPlugins++
return nil
}
// DisablePlugin disables a plugin
func (pr *PluginRegistry) DisablePlugin(name string) error {
pr.mu.Lock()
defer pr.mu.Unlock()
meta, exists := pr.metadata[name]
if !exists {
return fmt.Errorf("plugin not found: %s", name)
}
if !meta.Enabled {
return fmt.Errorf("plugin already disabled: %s", name)
}
meta.Enabled = false
pr.stats.EnabledPlugins--
return nil
}
// GetExecutionLog returns execution history for a plugin
func (pr *PluginRegistry) GetExecutionLog(name string) []*ExecutionRecord {
pr.mu.RLock()
defer pr.mu.RUnlock()
if log, exists := pr.executionLog[name]; exists {
result := make([]*ExecutionRecord, len(log))
copy(result, log)
return result
}
return make([]*ExecutionRecord, 0)
}
// GetStats returns registry statistics
func (pr *PluginRegistry) GetStats() *PluginStats {
pr.mu.RLock()
defer pr.mu.RUnlock()
stats := *pr.stats
if stats.TotalExecutions > 0 {
totalDuration := time.Duration(0)
for _, log := range pr.executionLog {
for _, record := range log {
totalDuration += record.Duration
}
}
stats.AverageExecutionTime = totalDuration / time.Duration(stats.TotalExecutions)
}
return &stats
}
// ResolvePluginURL resolves a plugin:// URL
func (pr *PluginRegistry) ResolvePluginURL(url string) (SkillPlugin, error) {
if len(url) < 9 || url[:9] != "plugin://" {
return nil, fmt.Errorf("invalid plugin URL: %s", url)
}
name := url[9:] // Remove "plugin://" prefix
plugin, exists := pr.Get(name)
if !exists {
return nil, fmt.Errorf("plugin not found: %s", name)
}
return plugin, nil
}
// Clear clears all plugins
func (pr *PluginRegistry) Clear() {
pr.mu.Lock()
defer pr.mu.Unlock()
pr.plugins = make(map[string]SkillPlugin)
pr.metadata = make(map[string]*PluginMetadata)
pr.executionLog = make(map[string][]*ExecutionRecord)
pr.stats = &PluginStats{}
}
// IsPluginURL checks if a URL is a plugin URL
func IsPluginURL(url string) bool {
return len(url) > 9 && url[:9] == "plugin://"
}
// ExtractPluginName extracts plugin name from plugin URL
func ExtractPluginName(url string) string {
if IsPluginURL(url) {
return url[9:]
}
return ""
}