Files
晨苒 627b673a5c refactor: multi-provider memory adapters with scan-based builtin (#227)
* refactor: restructure memory into multi-provider adapters, remove manifest.json dependency

- Rename internal/memory/provider to internal/memory/adapters with per-provider subdirectories (builtin, mem0, openviking)
- Replace manifest.json-based delete/update with scan-based index from daily files
- Add mem0 and openviking provider adapters with HTTP client, chat hooks, MCP tools, and CRUD
- Wire provider lifecycle into registry (auto-instantiate on create, evict on update/delete)
- Split docker-compose into base stack + optional overlays (qdrant, browser, mem0, openviking)
- Update admin UI to support dynamic provider config schema rendering

* chore(lint): fix all golangci-lint issues for clean CI

* refactor(docker): replace compose overlay files with profiles

* feat(memory): add built-in memory multi modes

* fix(ci): golangci lint

* feat(memory): edit built-in memory sparse design
2026-03-14 06:04:13 +08:00

104 lines
2.7 KiB
Go

package adapters
import (
"errors"
"fmt"
"log/slog"
"strings"
"sync"
)
// Factory creates a Provider from a provider type string and JSON config.
// The registry uses factories to lazily instantiate providers from DB rows.
type Factory func(id string, config map[string]any) (Provider, error)
// Registry manages provider instances keyed by their DB id.
// It caches instantiated providers and uses registered factories to create
// them on demand from stored configuration.
type Registry struct {
mu sync.RWMutex
instances map[string]Provider
factories map[string]Factory
logger *slog.Logger
}
func NewRegistry(log *slog.Logger) *Registry {
if log == nil {
log = slog.Default()
}
return &Registry{
instances: map[string]Provider{},
factories: map[string]Factory{},
logger: log.With(slog.String("component", "memory_provider_registry")),
}
}
// RegisterFactory registers a factory for a given provider type (e.g. "builtin").
func (r *Registry) RegisterFactory(providerType string, factory Factory) {
r.mu.Lock()
defer r.mu.Unlock()
r.factories[strings.TrimSpace(providerType)] = factory
}
// Register adds a pre-built provider instance by ID.
func (r *Registry) Register(id string, provider Provider) {
r.mu.Lock()
defer r.mu.Unlock()
r.instances[strings.TrimSpace(id)] = provider
}
// Get returns the provider for the given DB record ID.
func (r *Registry) Get(id string) (Provider, error) {
id = strings.TrimSpace(id)
if id == "" {
return nil, errors.New("provider id is required")
}
r.mu.RLock()
p, ok := r.instances[id]
r.mu.RUnlock()
if ok {
return p, nil
}
return nil, fmt.Errorf("memory provider not found: %s", id)
}
// Instantiate creates a provider from a DB row and caches it.
// If the instance already exists, it is returned directly.
func (r *Registry) Instantiate(id, providerType string, config map[string]any) (Provider, error) {
id = strings.TrimSpace(id)
providerType = strings.TrimSpace(providerType)
r.mu.RLock()
if p, ok := r.instances[id]; ok {
r.mu.RUnlock()
return p, nil
}
r.mu.RUnlock()
r.mu.Lock()
defer r.mu.Unlock()
// Double-check after acquiring write lock.
if p, ok := r.instances[id]; ok {
return p, nil
}
factory, ok := r.factories[providerType]
if !ok {
return nil, fmt.Errorf("unknown memory provider type: %s", providerType)
}
p, err := factory(id, config)
if err != nil {
return nil, fmt.Errorf("instantiate memory provider %s (%s): %w", id, providerType, err)
}
r.instances[id] = p
return p, nil
}
// Remove evicts a cached provider instance (e.g. after config update or delete).
func (r *Registry) Remove(id string) {
r.mu.Lock()
defer r.mu.Unlock()
delete(r.instances, strings.TrimSpace(id))
}