mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 08:57:57 +00:00
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
This commit is contained in:
85
internal/config/store.go
Normal file
85
internal/config/store.go
Normal file
@ -0,0 +1,85 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user