// Package backend owns validated, immutable OpenAI-compatible backend // definitions. package backend import ( "errors" "fmt" "gitea.maximumdirect.net/eric/promptkit/internal/domain" "gitea.maximumdirect.net/eric/promptkit/internal/jsonvalue" ) const ( // OpenRouterID identifies the maintained OpenRouter backend. OpenRouterID = "openrouter" // RakestrawHomeID identifies the maintained Rakestrawhome backend. RakestrawHomeID = "rakestrawhome" ) // ErrBackendNotFound identifies a registry lookup for an unknown backend ID. var ErrBackendNotFound = errors.New("backend not found") // Registry is an immutable collection of validated backend definitions. type Registry struct { backends map[string]domain.Backend } // NewEffectiveRegistry stores already-validated effective backend definitions // under their exact configuration keys. Configuration validation and // normalization precede this ownership boundary. func NewEffectiveRegistry(definitions map[string]domain.Backend) (*Registry, error) { registry := &Registry{backends: make(map[string]domain.Backend, len(definitions))} for id, definition := range definitions { params, err := jsonvalue.CopyMap(definition.ExtraParams) if err != nil { return nil, fmt.Errorf("backend %q extra parameters: %w", id, err) } definition.ID = id definition.ExtraParams = params registry.backends[id] = definition } return registry, nil } // GetBackend returns a defensive copy of the backend registered with id. func (r *Registry) GetBackend(id string) (domain.Backend, error) { if r == nil { return domain.Backend{}, fmt.Errorf("%w: %q", ErrBackendNotFound, id) } definition, ok := r.backends[id] if !ok { return domain.Backend{}, fmt.Errorf("%w: %q", ErrBackendNotFound, id) } extraParams, err := jsonvalue.CopyMap(definition.ExtraParams) if err != nil { return domain.Backend{}, fmt.Errorf("copy backend %q: %w", id, err) } definition.ExtraParams = extraParams return definition, nil } // CapacityPolicies returns a copy of the normalized policies for limited // backends. func (r *Registry) CapacityPolicies() map[string]domain.BackendCapacityPolicy { policies := make(map[string]domain.BackendCapacityPolicy) if r == nil { return policies } for id, definition := range r.backends { if definition.ConcurrencyLimit == 0 { continue } policies[id] = domain.BackendCapacityPolicy{ ConcurrencyLimit: definition.ConcurrencyLimit, QueueCapacity: definition.QueueCapacity, } } return policies }