- 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
193 lines
5.0 KiB
Go
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")
|
|
}
|