package node import ( "fmt" "iop/packages/go/config" iop "iop/proto/gen/iop" ) // BuildConfigPayload converts a NodeRecord's adapter config to the proto payload // sent to node during registration. // // After config normalisation, OllamaInstances, VllmInstances, and // OpenAICompatInstances already contain the promoted legacy single-instance // entries, so only the slice fields are iterated here. Duplicate instance names // produce an error. func BuildConfigPayload(rec *NodeRecord) (*iop.NodeConfigPayload, error) { payload := &iop.NodeConfigPayload{ Runtime: &iop.NodeRuntimeConfig{ Concurrency: int32(rec.Runtime.Concurrency), }, } payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{ Type: "mock", Enabled: true, Config: &iop.AdapterConfig_Mock{Mock: &iop.MockAdapterConfig{}}, }) seen := make(map[string]struct{}) for _, inst := range rec.Adapters.OllamaInstances { if !inst.Enabled { continue } key := "ollama:" + inst.Name if _, dup := seen[key]; dup { return nil, fmt.Errorf("duplicate ollama instance name %q", inst.Name) } seen[key] = struct{}{} payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{ Type: "ollama", Enabled: true, Name: inst.Name, Config: &iop.AdapterConfig_Ollama{ Ollama: &iop.OllamaAdapterConfig{ BaseUrl: inst.BaseURL, ContextSize: int32(inst.ContextSize), Capacity: int32(inst.Capacity), MaxQueue: int32(inst.MaxQueue), QueueTimeoutMs: int32(inst.QueueTimeoutMS), RequestTimeoutMs: int32(inst.RequestTimeoutMS), }, }, }) } for _, inst := range rec.Adapters.VllmInstances { if !inst.Enabled { continue } key := "vllm:" + inst.Name if _, dup := seen[key]; dup { return nil, fmt.Errorf("duplicate vllm instance name %q", inst.Name) } seen[key] = struct{}{} payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{ Type: "vllm", Enabled: true, Name: inst.Name, Config: &iop.AdapterConfig_Vllm{ Vllm: &iop.VllmAdapterConfig{ Endpoint: inst.Endpoint, Capacity: int32(inst.Capacity), MaxQueue: int32(inst.MaxQueue), QueueTimeoutMs: int32(inst.QueueTimeoutMS), RequestTimeoutMs: int32(inst.RequestTimeoutMS), }, }, }) } for _, inst := range rec.Adapters.OpenAICompatInstances { if !inst.Enabled { continue } key := "openai_compat:" + inst.Name if _, dup := seen[key]; dup { return nil, fmt.Errorf("duplicate openai_compat instance name %q", inst.Name) } seen[key] = struct{}{} payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{ Type: "openai_compat", Enabled: true, Name: inst.Name, Config: &iop.AdapterConfig_OpenaiCompat{ OpenaiCompat: &iop.OpenAICompatAdapterConfig{ Provider: inst.Provider, Endpoint: inst.Endpoint, Headers: inst.Headers, Capacity: int32(inst.Capacity), MaxQueue: int32(inst.MaxQueue), QueueTimeoutMs: int32(inst.QueueTimeoutMS), RequestTimeoutMs: int32(inst.RequestTimeoutMS), }, }, }) } if rec.Adapters.CLI.Enabled { profiles := make(map[string]*iop.CLIProfileConfig, len(rec.Adapters.CLI.Profiles)) for name, p := range rec.Adapters.CLI.Profiles { profiles[name] = cliProfileToProto(p) } payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{ Type: "cli", Enabled: true, Config: &iop.AdapterConfig_Cli{ Cli: &iop.CLIAdapterConfig{Profiles: profiles}, }, }) } return payload, nil } func cliProfileToProto(p config.CLIProfileConf) *iop.CLIProfileConfig { out := &iop.CLIProfileConfig{ Command: p.Command, Args: append([]string(nil), p.Args...), Env: append([]string(nil), p.Env...), Persistent: p.Persistent, Terminal: p.Terminal, ResponseIdleTimeoutMs: int32(p.ResponseIdleTimeoutMS), StartupIdleTimeoutMs: int32(p.StartupIdleTimeoutMS), OutputFormat: p.OutputFormat, Mode: p.Mode, ResumeArgs: append([]string(nil), p.ResumeArgs...), } if !p.CompletionMarker.Empty() { out.CompletionMarker = &iop.CLICompletionMarker{ Line: p.CompletionMarker.Line, Regex: p.CompletionMarker.Regex, } } return out }