Files
ModelRouter/internal/config/store.go
root f7f76e097d feat: ModelRouter — unified OpenAI-compatible multi-source LLM gateway
- Lua adapters per upstream (transform_request/response/stream_chunk, build_headers signing hooks)
- AUTO priority routing with per-model kind (chat/image), explicit source/model routing
- Per-source concurrency caps with queueing, exponential backoff, AUTO failover
- OpenAI-compatible API: chat completions, SSE streaming, image generations, models
- Gateway key auth, web UI for adapter/source management, runtime persistence
- e2e test running the real binary against mocked upstreams
2026-08-05 15:25:47 +08:00

85 lines
1.7 KiB
Go

package config
import (
"encoding/json"
"os"
"sync"
)
// Store persists web-UI editable runtime state (sources added/edited) to a
// JSON file so edits survive restarts. Base YAML sources are merged underneath.
type Store struct {
mu sync.Mutex
path string
data RuntimeConfig
}
func NewStore(path string) *Store {
return &Store{path: path}
}
// Load reads the runtime file (missing file = empty state).
func (s *Store) Load() error {
s.mu.Lock()
defer s.mu.Unlock()
data, err := os.ReadFile(s.path)
if err != nil {
if os.IsNotExist(err) {
s.data = RuntimeConfig{}
return nil
}
return err
}
return json.Unmarshal(data, &s.data)
}
// List returns the runtime sources (those edited via web UI).
func (s *Store) List() []Source {
s.mu.Lock()
defer s.mu.Unlock()
out := make([]Source, len(s.data.Sources))
copy(out, s.data.Sources)
return out
}
// Upsert adds or replaces a runtime source and persists.
func (s *Store) Upsert(src Source) error {
s.mu.Lock()
defer s.mu.Unlock()
for i := range s.data.Sources {
if s.data.Sources[i].Name == src.Name {
s.data.Sources[i] = src
return s.persistLocked()
}
}
s.data.Sources = append(s.data.Sources, src)
return s.persistLocked()
}
// Remove deletes a runtime source and persists.
func (s *Store) Remove(name string) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
kept := s.data.Sources[:0]
removed := false
for _, src := range s.data.Sources {
if src.Name == name {
removed = true
continue
}
kept = append(kept, src)
}
if !removed {
return false, nil
}
s.data.Sources = kept
return true, s.persistLocked()
}
func (s *Store) persistLocked() error {
b, err := json.MarshalIndent(s.data, "", " ")
if err != nil {
return err
}
return os.WriteFile(s.path, b, 0644)
}