// Package core wires together the Lua VM, provider registry, scheduler and // runtime store and exposes management operations (hot reload, adapters, // sources) for the web UI and gateway. package core import ( "fmt" "os" "path/filepath" "llmsproxy/internal/config" "llmsproxy/internal/lua" "llmsproxy/internal/provider" "llmsproxy/internal/scheduler" ) // Core owns the running configuration and adapters. type Core struct { cfg *config.Config vm *lua.VM store *config.Store scheduler *scheduler.Scheduler registry *provider.Registry } // New builds the core from a config file plus runtime overlay. func New(cfgPath string) (*Core, error) { cfg, err := config.Load(cfgPath) if err != nil { return nil, err } return NewFromConfig(cfg) } // NewFromConfig builds the core from an already-loaded config. func NewFromConfig(cfg *config.Config) (*Core, error) { c := &Core{cfg: cfg} c.vm = lua.NewVM(cfg.AdapterDir) if err := c.vm.Start(); err != nil { return nil, fmt.Errorf("lua vm: %w", err) } c.store = config.NewStore(cfg.RuntimeFile) if err := c.store.Load(); err != nil { return nil, fmt.Errorf("runtime store: %w", err) } c.scheduler = scheduler.New(buildRetries(cfg)) if err := c.rebuildRegistry(); err != nil { return nil, err } return c, nil } func buildRetries(cfg *config.Config) int { return len(cfg.Sources) // allow fallback across all sources } // VM exposes the Lua adapter runtime. func (c *Core) VM() *lua.VM { return c.vm } func (c *Core) Scheduler() *scheduler.Scheduler { return c.scheduler } func (c *Core) Registry() *provider.Registry { return c.registry } func (c *Core) DefaultModel() string { return c.cfg.DefaultModel } func (c *Core) GatewayKeys() []string { return c.cfg.GatewayKeys } func (c *Core) Listen() string { return c.cfg.Listen } // Config exposes the underlying configuration (read-only usage). func (c *Core) Config() *config.Config { return c.cfg } // mergedSources = base YAML sources + runtime sources (runtime wins by name). func (c *Core) mergedSources() []config.Source { byName := map[string]config.Source{} order := []string{} for _, s := range c.cfg.Sources { byName[s.Name] = s order = append(order, s.Name) } for _, s := range c.store.List() { if _, ok := byName[s.Name]; !ok { order = append(order, s.Name) } byName[s.Name] = s } out := make([]config.Source, 0, len(order)) seen := map[string]bool{} for _, n := range order { if !seen[n] { seen[n] = true out = append(out, byName[n]) } } return out } func (c *Core) rebuildRegistry() error { srcs := c.mergedSources() providers := make([]*provider.Provider, 0, len(srcs)) adapterConcurrency := map[string]int{} for _, s := range srcs { providers = append(providers, provider.New(s, c.vm)) if s.Adapter != "" { adapterConcurrency[s.Adapter] += s.MaxConcurrent } } c.vm.ConfigureConcurrency(adapterConcurrency) if c.registry == nil { c.registry = provider.NewRegistry(providers, c.cfg.DefaultModel) } else { c.registry.Replace(providers) } return nil } // Reload re-reads the runtime store and rebuilds sources (adapter reload is not // strictly needed since adapters are loaded into the VM at startup; uploaded // adapters are placed in the adapter dir and loaded by the web UI). func (c *Core) Reload() error { if err := c.store.Load(); err != nil { return err } return c.rebuildRegistry() } // ---- adapter management (web UI) ---- func (c *Core) ListAdapters() []lua.APIAdapter { return c.vm.ListAdapters() } // UploadAdapter saves a new Lua adapter script to the adapter dir and loads it. func (c *Core) UploadAdapter(name, code string) error { if name == "" { return fmt.Errorf("adapter name required") } if err := os.MkdirAll(c.cfg.AdapterDir, 0755); err != nil { return err } path := filepath.Join(c.cfg.AdapterDir, name+".lua") if err := os.WriteFile(path, []byte(code), 0644); err != nil { return err } if err := c.vm.LoadAdapter(path); err != nil { return fmt.Errorf("load adapter: %w", err) } return nil } // RemoveAdapter deletes an adapter script and evicts it from the VM. func (c *Core) RemoveAdapter(name string) error { path := filepath.Join(c.cfg.AdapterDir, name+".lua") _ = os.Remove(path) c.vm.RemoveAdapter(name) return nil } // ---- source management (web UI) ---- func (c *Core) AddSource(src config.Source) error { if err := normalizeSource(&src); err != nil { return err } if err := c.store.Upsert(src); err != nil { return err } return c.rebuildRegistry() } func (c *Core) RemoveSource(name string) error { if _, err := c.store.Remove(name); err != nil { return err } return c.rebuildRegistry() } func (c *Core) Sources() []config.Source { return c.mergedSources() } func normalizeSource(s *config.Source) error { if s.Name == "" || s.BaseURL == "" { return fmt.Errorf("source requires name and base_url") } if len(s.Models) == 0 { return fmt.Errorf("source requires at least one model") } if s.Adapter == "" { s.Adapter = "openai" } if s.MaxConcurrent == 0 { s.MaxConcurrent = 8 } return nil } // Close releases resources. func (c *Core) Close() { if c.vm != nil { c.vm.Stop() } }