package adapters import ( "encoding/json" "fmt" "sort" "go.uber.org/zap" "iop/apps/node/internal/adapters/cli" "iop/apps/node/internal/adapters/mock" "iop/apps/node/internal/adapters/ollama" "iop/apps/node/internal/adapters/openai_compat" "iop/apps/node/internal/adapters/vllm" "iop/packages/go/config" iop "iop/proto/gen/iop" ) type ConfigSet struct { Registry *Registry Items map[string]ConfigItem Runtime RuntimeConfig } // RuntimeConfig captures node-wide runtime tuning carried alongside the adapter // set so config refresh can detect concurrency changes. type RuntimeConfig struct { Concurrency int } type ConfigItem struct { Key string Type string Fingerprint string } type ConfigDiff struct { Added []string Updated []string Removed []string Unchanged []string } func BuildConfigSet(payload *iop.NodeConfigPayload, logger *zap.Logger) (*ConfigSet, error) { reg := NewRegistry() items := make(map[string]ConfigItem) for _, ac := range payload.GetAdapters() { if !ac.GetEnabled() { continue } instanceKey := ac.GetName() typeName := ac.GetType() if instanceKey == "" { instanceKey = typeName } var fingerprint string switch typeName { case "mock": a := mock.New(logger) reg.RegisterKeyed(instanceKey, typeName, a) fingerprint = "mock" case "ollama": var cfg config.OllamaConf if m := ac.GetOllama(); m != nil { cfg = ollamaConfFromProto(m) } else { cfg = ollamaConfFromStruct(ac.GetSettings()) } a := ollama.New(cfg, logger, instanceKey) reg.RegisterKeyed(instanceKey, typeName, a) bs, err := json.Marshal(cfg) if err != nil { return nil, fmt.Errorf("marshal ollama config: %w", err) } fingerprint = string(bs) case "vllm": var cfg config.VllmConf if m := ac.GetVllm(); m != nil { cfg = vllmConfFromProto(m) } else { cfg = vllmConfFromStruct(ac.GetSettings()) } a := vllm.New(cfg, logger, instanceKey) reg.RegisterKeyed(instanceKey, typeName, a) bs, err := json.Marshal(cfg) if err != nil { return nil, fmt.Errorf("marshal vllm config: %w", err) } fingerprint = string(bs) case "openai_compat": var cfg config.OpenAICompatConf if m := ac.GetOpenaiCompat(); m != nil { cfg = openAICompatConfFromProto(m) } else { cfg = openAICompatConfFromStruct(ac.GetSettings()) } a := openai_compat.New(cfg, logger, instanceKey) reg.RegisterKeyed(instanceKey, typeName, a) bs, err := json.Marshal(cfg) if err != nil { return nil, fmt.Errorf("marshal openai_compat config: %w", err) } fingerprint = string(bs) case "cli": var cfg config.CLIConf if m := ac.GetCli(); m != nil { cfg = cliConfFromProto(m) } else { cfg = cliConfFromStruct(ac.GetSettings()) } a := cli.New(cfg, logger) reg.RegisterKeyed(instanceKey, typeName, a) bs, err := json.Marshal(cfg) if err != nil { return nil, fmt.Errorf("marshal cli config: %w", err) } fingerprint = string(bs) default: return nil, fmt.Errorf("adapters: unknown adapter type %q", typeName) } items[instanceKey] = ConfigItem{ Key: instanceKey, Type: typeName, Fingerprint: fingerprint, } } return &ConfigSet{ Registry: reg, Items: items, Runtime: RuntimeConfig{ Concurrency: int(payload.GetRuntime().GetConcurrency()), }, }, nil } func DiffConfigSets(current, next *ConfigSet) ConfigDiff { var diff ConfigDiff currItems := make(map[string]ConfigItem) if current != nil { currItems = current.Items } nextItems := make(map[string]ConfigItem) if next != nil { nextItems = next.Items } // Added / Updated / Unchanged for k, nextItem := range nextItems { currItem, exists := currItems[k] if !exists { diff.Added = append(diff.Added, k) } else if currItem.Type != nextItem.Type || currItem.Fingerprint != nextItem.Fingerprint { diff.Updated = append(diff.Updated, k) } else { diff.Unchanged = append(diff.Unchanged, k) } } // Removed for k := range currItems { if _, exists := nextItems[k]; !exists { diff.Removed = append(diff.Removed, k) } } sort.Strings(diff.Added) sort.Strings(diff.Updated) sort.Strings(diff.Removed) sort.Strings(diff.Unchanged) return diff }