package plugins import ( "context" "encoding/json" "fmt" "sync" "github.com/muyue/muyue/internal/agent" ) type PluginStatus string const ( StatusEnabled PluginStatus = "enabled" StatusDisabled PluginStatus = "disabled" StatusError PluginStatus = "error" ) type Plugin struct { name string version string description string status PluginStatus tools []*agent.ToolDefinition hooks map[HookType]HookFunc init func(ctx context.Context, registry *agent.Registry) error } func NewPlugin(name, version, description string) *Plugin { return &Plugin{ name: name, version: version, description: description, status: StatusDisabled, tools: make([]*agent.ToolDefinition, 0), hooks: make(map[HookType]HookFunc), } } func (p *Plugin) Name() string { return p.name } func (p *Plugin) Version() string { return p.version } func (p *Plugin) Description() string { return p.description } func (p *Plugin) Status() PluginStatus { return p.status } func (p *Plugin) AddTool(tool *ToolDefinition) *Plugin { td := &agent.ToolDefinition{ Name: tool.Name, Description: tool.Description, Params: tool.Params, Handler: tool.Handler, } p.tools = append(p.tools, td) return p } func (p *Plugin) AddToolGeneric(params interface{}, name, description string, handler func(ctx context.Context, raw json.RawMessage) (agent.ToolResponse, error)) *Plugin { paramsSchema, err := generatePluginSchema(params) if err == nil { td := &agent.ToolDefinition{ Name: name, Description: description, Params: paramsSchema, Handler: handler, } p.tools = append(p.tools, td) } return p } func (p *Plugin) AddHook(hookType HookType, fn HookFunc) *Plugin { p.hooks[hookType] = fn return p } func (p *Plugin) SetInit(fn func(ctx context.Context, registry *agent.Registry) error) *Plugin { p.init = fn return p } type ToolDefinition struct { Name string Description string Params json.RawMessage Handler func(ctx context.Context, args json.RawMessage) (agent.ToolResponse, error) } type PluginInfo struct { Name string `json:"name"` Version string `json:"version"` Description string `json:"description"` Status PluginStatus `json:"status"` ToolCount int `json:"tool_count"` HookTypes []string `json:"hook_types,omitempty"` Error string `json:"error,omitempty"` } type Manager struct { mu sync.RWMutex plugins map[string]*Plugin hooks *HookRegistry enabled map[string]bool } func NewManager(hooks *HookRegistry) *Manager { return &Manager{ plugins: make(map[string]*Plugin), hooks: hooks, enabled: make(map[string]bool), } } func (m *Manager) Register(p *Plugin) error { m.mu.Lock() defer m.mu.Unlock() if _, exists := m.plugins[p.name]; exists { return fmt.Errorf("plugin %q already registered", p.name) } m.plugins[p.name] = p return nil } func (m *Manager) Enable(ctx context.Context, name string, registry *agent.Registry) error { m.mu.Lock() defer m.mu.Unlock() p, ok := m.plugins[name] if !ok { return fmt.Errorf("plugin %q not found", name) } if p.status == StatusEnabled { return nil } if p.init != nil { if err := p.init(ctx, registry); err != nil { p.status = StatusError return fmt.Errorf("plugin %q init failed: %w", name, err) } } for _, tool := range p.tools { if err := registry.Register(tool); err != nil { p.status = StatusError return fmt.Errorf("plugin %q register tool %q: %w", name, tool.Name, err) } } for hookType, fn := range p.hooks { m.hooks.Register(hookType, name, 10, fn) } p.status = StatusEnabled m.enabled[name] = true return nil } func (m *Manager) Disable(name string) { m.mu.Lock() defer m.mu.Unlock() p, ok := m.plugins[name] if !ok { return } if p.status != StatusEnabled { return } m.hooks.RemoveByPlugin(name) p.status = StatusDisabled delete(m.enabled, name) } func (m *Manager) Get(name string) (*Plugin, bool) { m.mu.RLock() defer m.mu.RUnlock() p, ok := m.plugins[name] return p, ok } func (m *Manager) List() []PluginInfo { m.mu.RLock() defer m.mu.RUnlock() result := make([]PluginInfo, 0, len(m.plugins)) for _, p := range m.plugins { info := PluginInfo{ Name: p.name, Version: p.version, Description: p.description, Status: p.status, ToolCount: len(p.tools), } for ht := range p.hooks { info.HookTypes = append(info.HookTypes, string(ht)) } result = append(result, info) } return result } func (m *Manager) EnabledNames() []string { m.mu.RLock() defer m.mu.RUnlock() names := make([]string, 0, len(m.enabled)) for name := range m.enabled { names = append(names, name) } return names } func (m *Manager) EnableFromConfig(ctx context.Context, enabledList []string, registry *agent.Registry) { for _, name := range enabledList { if err := m.Enable(ctx, name, registry); err != nil { _ = err } } }