feat: config.yaml only, OpenRouter free models, remove runtime.json config

- Move all config (auto rules, keys) from runtime.json to config.yaml
- Store now only holds runtime sources (WebUI-created)
- Add OpenRouter free models to config.yaml
- One-time migration from legacy runtime.json on startup
- Fix gateway tests for new config structure
- Update core.go with migrateFromRuntime, saveConfig, seedKeys/seedAuto
- Remove SaveKey/KeyByValue/AutoRules from Store
- Add Config.Save() with YAML marshaling
- Update WebUI admin keys visibility (show all keys including admin)
- Bump binary to 11MB with luajit
This commit is contained in:
JianFeeeee
2026-08-13 10:18:19 +08:00
parent 084c9fee2b
commit d06210204b
10 changed files with 307 additions and 225 deletions

View File

@ -14,12 +14,12 @@ import (
type Registry struct {
mu sync.RWMutex
providers []*Provider
byModel map[string]*Provider // modelID -> provider
defaultM string // default model id ("" means AUTO)
byModel map[string][]*Provider // modelID -> providers (one per source)
defaultM string // default model id ("" means AUTO)
}
func NewRegistry(providers []*Provider, defaultModel string) *Registry {
r := &Registry{byModel: map[string]*Provider{}, defaultM: defaultModel}
r := &Registry{byModel: map[string][]*Provider{}, defaultM: defaultModel}
r.set(providers)
return r
}
@ -33,12 +33,14 @@ func (r *Registry) Replace(providers []*Provider) {
func (r *Registry) set(providers []*Provider) {
r.providers = providers
r.byModel = map[string]*Provider{}
m := map[string][]*Provider{}
for _, p := range providers {
for _, m := range p.Models() {
r.byModel[strings.ToLower(m)] = p
for _, model := range p.Models() {
key := strings.ToLower(model)
m[key] = append(m[key], p)
}
}
r.byModel = m
}
func (r *Registry) Providers() []*Provider {
@ -102,10 +104,11 @@ func (r *Registry) Resolve(model string) []*Provider {
if p := r.ResolvePinned(model); p != nil {
return []*Provider{p}
}
// explicit model
if p, ok := r.byModel[strings.ToLower(model)]; ok {
// switch to the owning source but pin the model via request
return []*Provider{p}
// explicit model — may be served by multiple sources
if ps, ok := r.byModel[strings.ToLower(model)]; ok {
out := make([]*Provider, len(ps))
copy(out, ps)
return out
}
return nil
}
@ -151,18 +154,20 @@ func (r *Registry) ResolvePinned(model string) *Provider {
}
// ProviderForModel returns the provider owning the model id (nil if unknown).
// When the same model exists on multiple sources, returns the first (source
// config order). Callers that need a specific source use ProviderForSlot.
func (r *Registry) ProviderForModel(model string) *Provider {
r.mu.RLock()
defer r.mu.RUnlock()
p, ok := r.byModel[strings.ToLower(strings.TrimSpace(model))]
if !ok {
ps, ok := r.byModel[strings.ToLower(strings.TrimSpace(model))]
if !ok || len(ps) == 0 {
return nil
}
return p
return ps[0]
}
// ProviderForSlot returns the provider for a (model, source) slot. When
// source is empty it behaves like ProviderForModel (owner of the model id);
// source is empty it behaves like ProviderForModel (first owner of the model id);
// when source is set it returns only that exact source (nil if the source
// does not serve the model).
func (r *Registry) ProviderForSlot(model, source string) *Provider {
@ -171,11 +176,11 @@ func (r *Registry) ProviderForSlot(model, source string) *Provider {
r.mu.RLock()
defer r.mu.RUnlock()
if source == "" {
p, ok := r.byModel[model]
if !ok {
ps, ok := r.byModel[model]
if !ok || len(ps) == 0 {
return nil
}
return p
return ps[0]
}
for _, p := range r.providers {
if !strings.EqualFold(p.Name(), source) {