- Add validate_test.go for edge validation - Update config.go and config_test.go with new config handling - Update roadmap and SDD documents for operational observability provider management - Update edge node mapper and config tests - Add inflight accounting recovery task and archive
250 lines
7.7 KiB
Go
250 lines
7.7 KiB
Go
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.
|
|
//
|
|
// Provider-first entries in rec.Providers are compiled first: each provider id
|
|
// becomes the adapter instance key. Legacy adapter instances in rec.Adapters are
|
|
// appended afterwards. An instance key that appears in both produces an error.
|
|
// Providers with a non-empty Adapter field reference a legacy instance and are
|
|
// skipped during provider-first compile.
|
|
//
|
|
// 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{}},
|
|
})
|
|
|
|
// flat namespace to detect conflicts between provider ids and legacy adapter keys
|
|
seenFlat := make(map[string]struct{})
|
|
|
|
for _, p := range rec.Providers {
|
|
if p.Adapter != "" {
|
|
// references a legacy adapter instance; skip provider-first compile
|
|
continue
|
|
}
|
|
if _, dup := seenFlat[p.ID]; dup {
|
|
return nil, fmt.Errorf("duplicate provider id %q", p.ID)
|
|
}
|
|
seenFlat[p.ID] = struct{}{}
|
|
ac, err := providerToAdapterConfig(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
payload.Adapters = append(payload.Adapters, ac)
|
|
}
|
|
|
|
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{}{}
|
|
if _, conflict := seenFlat[inst.Name]; conflict {
|
|
return nil, fmt.Errorf("ollama adapter instance key %q conflicts with provider id", inst.Name)
|
|
}
|
|
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{}{}
|
|
if _, conflict := seenFlat[inst.Name]; conflict {
|
|
return nil, fmt.Errorf("vllm adapter instance key %q conflicts with provider id", inst.Name)
|
|
}
|
|
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{}{}
|
|
if _, conflict := seenFlat[inst.Name]; conflict {
|
|
return nil, fmt.Errorf("openai_compat adapter instance key %q conflicts with provider id", inst.Name)
|
|
}
|
|
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 {
|
|
if _, conflict := seenFlat["cli"]; conflict {
|
|
return nil, fmt.Errorf("cli adapter instance key %q conflicts with provider id", "cli")
|
|
}
|
|
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
|
|
}
|
|
|
|
// providerToAdapterConfig compiles a provider-first NodeProviderConf into an
|
|
// AdapterConfig using the provider id as the instance key.
|
|
func providerToAdapterConfig(p config.NodeProviderConf) (*iop.AdapterConfig, error) {
|
|
typeName := config.NormalizeProviderType(p.Type)
|
|
switch typeName {
|
|
case "ollama":
|
|
return &iop.AdapterConfig{
|
|
Type: "ollama",
|
|
Enabled: true,
|
|
Name: p.ID,
|
|
Config: &iop.AdapterConfig_Ollama{
|
|
Ollama: &iop.OllamaAdapterConfig{
|
|
BaseUrl: p.BaseURL,
|
|
ContextSize: int32(p.ContextSize),
|
|
Capacity: int32(p.Capacity),
|
|
MaxQueue: int32(p.MaxQueue),
|
|
QueueTimeoutMs: int32(p.QueueTimeoutMS),
|
|
RequestTimeoutMs: int32(p.RequestTimeoutMS),
|
|
},
|
|
},
|
|
}, nil
|
|
|
|
case "openai_compat":
|
|
return &iop.AdapterConfig{
|
|
Type: "openai_compat",
|
|
Enabled: true,
|
|
Name: p.ID,
|
|
Config: &iop.AdapterConfig_OpenaiCompat{
|
|
OpenaiCompat: &iop.OpenAICompatAdapterConfig{
|
|
Provider: p.Provider,
|
|
Endpoint: p.Endpoint,
|
|
Headers: p.Headers,
|
|
Capacity: int32(p.Capacity),
|
|
MaxQueue: int32(p.MaxQueue),
|
|
QueueTimeoutMs: int32(p.QueueTimeoutMS),
|
|
RequestTimeoutMs: int32(p.RequestTimeoutMS),
|
|
},
|
|
},
|
|
}, nil
|
|
|
|
case "cli":
|
|
// provider id becomes both the adapter instance key and the profile key
|
|
return &iop.AdapterConfig{
|
|
Type: "cli",
|
|
Enabled: true,
|
|
Name: p.ID,
|
|
Config: &iop.AdapterConfig_Cli{
|
|
Cli: &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
p.ID: {
|
|
Command: p.Command,
|
|
Args: append([]string(nil), p.Args...),
|
|
Env: append([]string(nil), p.Env...),
|
|
Mode: p.Mode,
|
|
OutputFormat: p.OutputFormat,
|
|
ResumeArgs: append([]string(nil), p.ResumeArgs...),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("provider %q: unsupported type %q for provider-first compile", p.ID, p.Type)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|