// Package registry provides the global capability registry. Every capability a // character can use — tools, skills, channels, MCP servers and IoT providers — // is registered here so it is globally shared and always available to any // character and mode (DevDocs/design/01-角色人格与行为模式.md §4). // // It is the single source of truth that tool dispatch (M2) and plugin/skill/MCP // wiring (M5) read from. The registry is safe for concurrent use; it implements // the pkg/plugins/sdk Registrar interface so plugins register through the SDK // helpers without knowing this concrete type. package registry import ( "errors" "fmt" "sync" "git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk" ) // Kind classifies a registered capability. type Kind string const ( // KindTool is a callable function with a JSON Schema (sdk.Tool). KindTool Kind = "tool" // KindSkill is a reusable workflow with steps, prompts, refs and guardrails // (sdk.Skill). KindSkill Kind = "skill" // KindChannel is a platform adapter (sdk.Channel). KindChannel Kind = "channel" // KindMCP is a capability exposed by an MCP server (subsystem-owned). KindMCP Kind = "mcp" // KindIoT is an IoT device or provider capability (subsystem-owned). KindIoT Kind = "iot" ) // Capability is a globally shared, always-available capability registered in // the registry. type Capability struct { // Name is the unique capability identifier. Name string // Kind classifies the capability. Kind Kind // Description is a human-readable summary used for selection and display. Description string // Value is the underlying capability object. For KindTool/KindSkill/ // KindChannel it is the SDK interface from pkg/plugins/sdk; for KindMCP and // KindIoT it is owned by the relevant subsystem. Value any } // Registry is the thread-safe global capability registry. type Registry struct { mu sync.RWMutex caps map[string]Capability } // Compile-time assertion that *Registry satisfies the SDK Registrar interface. var _ sdk.Registrar = (*Registry)(nil) // Sentinel errors returned by the registry. Use errors.Is to test for them. var ( // ErrNotFound is returned by Get when no capability has the given name. ErrNotFound = errors.New("registry: capability not found") // ErrDuplicate is returned by Register when a capability with the same name // is already registered. ErrDuplicate = errors.New("registry: capability already registered") // ErrInvalidArgument is returned when a capability has an empty name, an // unknown kind, or a nil Value. ErrInvalidArgument = errors.New("registry: invalid capability") ) // New returns an empty, concurrency-safe registry. func New() *Registry { return &Registry{caps: make(map[string]Capability)} } // Register adds cap to the registry. It returns ErrInvalidArgument when the // name is empty, the kind is unknown or the Value is nil, and ErrDuplicate when // a capability with the same name already exists. func (r *Registry) Register(cap Capability) error { if cap.Name == "" || !validKind(cap.Kind) || cap.Value == nil { return fmt.Errorf("registry: register %q: %w", cap.Name, ErrInvalidArgument) } r.mu.Lock() defer r.mu.Unlock() if _, exists := r.caps[cap.Name]; exists { return fmt.Errorf("registry: register %q: %w", cap.Name, ErrDuplicate) } r.caps[cap.Name] = cap return nil } // Get returns the capability registered under name. It returns ErrNotFound when // no such capability is registered. func (r *Registry) Get(name string) (Capability, error) { r.mu.RLock() defer r.mu.RUnlock() cap, ok := r.caps[name] if !ok { return Capability{}, fmt.Errorf("registry: get %q: %w", name, ErrNotFound) } return cap, nil } // List returns a copy of the capabilities whose Kind matches kind. An empty // kind returns every registered capability. The returned slice is owned by the // caller and safe to mutate. func (r *Registry) List(kind Kind) []Capability { r.mu.RLock() defer r.mu.RUnlock() out := make([]Capability, 0, len(r.caps)) for _, cap := range r.caps { if kind != "" && cap.Kind != kind { continue } out = append(out, cap) } return out } // RegisterTool registers t as a KindTool capability. func (r *Registry) RegisterTool(t sdk.Tool) error { if t == nil { return fmt.Errorf("registry: register tool: %w", ErrInvalidArgument) } return r.Register(Capability{ Name: t.Name(), Kind: KindTool, Description: t.Description(), Value: t, }) } // RegisterSkill registers s as a KindSkill capability. func (r *Registry) RegisterSkill(s sdk.Skill) error { if s == nil { return fmt.Errorf("registry: register skill: %w", ErrInvalidArgument) } return r.Register(Capability{ Name: s.Name(), Kind: KindSkill, Description: s.Description(), Value: s, }) } // RegisterChannel registers c as a KindChannel capability. func (r *Registry) RegisterChannel(c sdk.Channel) error { if c == nil { return fmt.Errorf("registry: register channel: %w", ErrInvalidArgument) } return r.Register(Capability{ Name: c.Name(), Kind: KindChannel, Description: c.Name(), Value: c, }) } // validKind reports whether k is one of the known capability kinds. func validKind(k Kind) bool { switch k { case KindTool, KindSkill, KindChannel, KindMCP, KindIoT: return true default: return false } }