package config import ( "encoding/json" "fmt" "os" "sync" ) // ProviderData mirrors the Gateway ProviderConfig JSON shape. type ProviderData struct { Name string `json:"name"` BaseURL string `json:"base_url"` APIKey string `json:"api_key"` TimeoutSec int `json:"timeout_sec"` MaxRetries int `json:"max_retries"` APIVersion string `json:"api_version,omitempty"` ExtraHeaders map[string]string `json:"extra_headers,omitempty"` } // ModelData mirrors the Gateway ModelConfig JSON shape. type ModelData struct { ID string `json:"id"` Name string `json:"name"` Provider string `json:"provider"` Description string `json:"description"` Priority int `json:"priority"` Tags []string `json:"tags"` Params map[string]interface{} `json:"params"` Enabled bool `json:"enabled"` } // RoutingData mirrors the Gateway RoutingRule JSON shape. type RoutingData struct { Purpose string `json:"purpose"` FallbackChain []string `json:"fallback_chain"` Required bool `json:"required"` } // ModelsConfigData is the top-level config document (read-only mirror). type ModelsConfigData struct { Version string `json:"version"` Providers map[string]*ProviderData `json:"providers"` Models map[string]*ModelData `json:"models"` Routing map[string]*RoutingData `json:"routing"` } // Loader provides read-only access to models.json. type Loader struct { mu sync.RWMutex path string config *ModelsConfigData } // NewLoader reads models.json and returns a Loader. Returns nil config if file doesn't exist. func NewLoader(path string) (*Loader, error) { l := &Loader{ path: path, config: &ModelsConfigData{ Version: "1.0", Providers: make(map[string]*ProviderData), Models: make(map[string]*ModelData), Routing: make(map[string]*RoutingData), }, } if err := l.load(); err != nil { return nil, err } return l, nil } func (l *Loader) load() error { data, err := os.ReadFile(l.path) if err != nil { if os.IsNotExist(err) { l.config = nil // Signal: use .env fallback. return nil } return fmt.Errorf("read model config: %w", err) } if len(data) == 0 { l.config = nil return nil } var cfg ModelsConfigData if err := json.Unmarshal(data, &cfg); err != nil { return fmt.Errorf("parse model config: %w", err) } if cfg.Providers == nil { cfg.Providers = make(map[string]*ProviderData) } if cfg.Models == nil { cfg.Models = make(map[string]*ModelData) } if cfg.Routing == nil { cfg.Routing = make(map[string]*RoutingData) } l.config = &cfg return nil } // HasConfig returns true if models.json exists and contains data. func (l *Loader) HasConfig() bool { l.mu.RLock() defer l.mu.RUnlock() return l.config != nil && (len(l.config.Providers) > 0 || len(l.config.Models) > 0) } // Reload re-reads the config file. Used for config updates without restart. func (l *Loader) Reload() error { return l.load() } // GetConfig returns the current config (read-only). func (l *Loader) GetConfig() *ModelsConfigData { l.mu.RLock() defer l.mu.RUnlock() return l.config }