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 "" }