Files
Cyrene_v2/backend/internal/plugin/registry.go
T
Aska 750d594055 feat(plugin): add plugin interface system and iot-mock implementation
- internal/plugin: Plugin/IoTProvider/ToolProvider/RAGProvider interfaces
- internal/plugin: Registry with type-safe auto-detection, 11 tests
- plugins/iot-mock: 8 simulated smart home devices, 11 tests
- 63 total tests, build + vet clean
2026-08-09 20:38:37 +08:00

193 lines
5.0 KiB
Go

package plugin
import (
"context"
"fmt"
"log"
"sync"
)
// Registry manages all loaded plugins and provides type-safe access to providers.
type Registry struct {
mu sync.RWMutex
plugins map[string]Plugin
iotProviders []IoTProvider
toolProviders []ToolProvider
ragProviders []RAGProvider
}
// NewRegistry creates a new empty plugin registry.
func NewRegistry() *Registry {
return &Registry{
plugins: make(map[string]Plugin),
iotProviders: make([]IoTProvider, 0),
toolProviders: make([]ToolProvider, 0),
ragProviders: make([]RAGProvider, 0),
}
}
// Register adds a plugin to the registry after calling Init.
// If Init fails or the plugin is disabled, it is not added.
func (r *Registry) Register(p Plugin, cfg PluginConfig) error {
ctx := context.Background()
if err := p.Init(ctx, cfg); err != nil {
return fmt.Errorf("plugin %s init failed: %w", p.Name(), err)
}
if !p.Enabled() {
log.Printf("[plugin] %s is disabled, skipping", p.Name())
return nil
}
r.mu.Lock()
defer r.mu.Unlock()
// Check for duplicate
if _, exists := r.plugins[p.Name()]; exists {
return fmt.Errorf("plugin %s already registered", p.Name())
}
r.plugins[p.Name()] = p
// Auto-detect provider interfaces
if iot, ok := p.(IoTProvider); ok {
r.iotProviders = append(r.iotProviders, iot)
log.Printf("[plugin] registered IoT provider: %s v%s", p.Name(), p.Version())
}
if tool, ok := p.(ToolProvider); ok {
r.toolProviders = append(r.toolProviders, tool)
log.Printf("[plugin] registered tool provider: %s v%s (%d tools)", p.Name(), p.Version(), len(tool.Tools()))
}
if rag, ok := p.(RAGProvider); ok {
r.ragProviders = append(r.ragProviders, rag)
log.Printf("[plugin] registered RAG provider: %s v%s", p.Name(), p.Version())
}
log.Printf("[plugin] %s v%s loaded successfully", p.Name(), p.Version())
return nil
}
// Get returns a plugin by name, or nil if not found.
func (r *Registry) Get(name string) Plugin {
r.mu.RLock()
defer r.mu.RUnlock()
return r.plugins[name]
}
// ListNames returns the names of all loaded plugins.
func (r *Registry) ListNames() []string {
r.mu.RLock()
defer r.mu.RUnlock()
names := make([]string, 0, len(r.plugins))
for name := range r.plugins {
names = append(names, name)
}
return names
}
// GetIoTProvider returns the first registered IoT provider, or nil.
// When multiple IoT providers are registered, the first one wins.
func (r *Registry) GetIoTProvider() IoTProvider {
r.mu.RLock()
defer r.mu.RUnlock()
if len(r.iotProviders) > 0 {
return r.iotProviders[0]
}
return nil
}
// GetAllToolProviders returns all registered tool providers.
func (r *Registry) GetAllToolProviders() []ToolProvider {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]ToolProvider, len(r.toolProviders))
copy(result, r.toolProviders)
return result
}
// GetAllToolDefinitions returns a flat list of all tool definitions from all tool providers.
func (r *Registry) GetAllToolDefinitions() []ToolDefinition {
r.mu.RLock()
defer r.mu.RUnlock()
defs := make([]ToolDefinition, 0)
for _, tp := range r.toolProviders {
defs = append(defs, tp.Tools()...)
}
return defs
}
// ExecuteTool finds a tool by name across all tool providers and executes it.
func (r *Registry) ExecuteTool(ctx context.Context, toolName string, params map[string]any) (string, error) {
r.mu.RLock()
defer r.mu.RUnlock()
for _, tp := range r.toolProviders {
for _, def := range tp.Tools() {
if def.Name == toolName {
return tp.Execute(ctx, toolName, params)
}
}
}
return "", fmt.Errorf("tool %s not found in any provider", toolName)
}
// GetRAGProviders returns all registered RAG providers.
func (r *Registry) GetRAGProviders() []RAGProvider {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]RAGProvider, len(r.ragProviders))
copy(result, r.ragProviders)
return result
}
// Count returns the total number of loaded plugins.
func (r *Registry) Count() int {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.plugins)
}
// Shutdown calls Close on all plugins in reverse registration order.
func (r *Registry) Shutdown() {
r.mu.Lock()
defer r.mu.Unlock()
// Close base plugins that are not specialized providers
for _, p := range r.plugins {
// Check if it was already closed via a provider list
if _, ok := p.(IoTProvider); ok {
continue
}
if _, ok := p.(ToolProvider); ok {
continue
}
if _, ok := p.(RAGProvider); ok {
continue
}
if err := p.Close(); err != nil {
log.Printf("[plugin] error closing %s: %v", p.Name(), err)
}
}
// Close IoT providers
for _, p := range r.iotProviders {
if err := p.Close(); err != nil {
log.Printf("[plugin] error closing %s: %v", p.Name(), err)
}
}
// Close tool providers
for _, p := range r.toolProviders {
if err := p.Close(); err != nil {
log.Printf("[plugin] error closing %s: %v", p.Name(), err)
}
}
// Close RAG providers
for _, p := range r.ragProviders {
if err := p.Close(); err != nil {
log.Printf("[plugin] error closing %s: %v", p.Name(), err)
}
}
log.Printf("[plugin] all plugins shut down")
}