iop/apps/node/internal/adapters/factory.go
toki f873e27a15 feat: CLI profile proto message support
- Add runtime proto for CLI profile messages
- Update edge transport server with profile support
- Update node adapters factory for profile handling
- Regenerate protobuf code
2026-05-04 22:54:23 +09:00

210 lines
5.2 KiB
Go

package adapters
import (
"fmt"
"go.uber.org/zap"
"google.golang.org/protobuf/types/known/structpb"
"iop/apps/node/internal/adapters/cli"
"iop/apps/node/internal/adapters/mock"
"iop/apps/node/internal/adapters/ollama"
"iop/apps/node/internal/adapters/vllm"
"iop/packages/config"
iop "iop/proto/gen/iop"
)
// BuildFromPayload creates a Registry from a NodeConfigPayload received from edge.
func BuildFromPayload(payload *iop.NodeConfigPayload, logger *zap.Logger) (*Registry, error) {
reg := NewRegistry()
reg.Register(mock.New(logger))
for _, ac := range payload.GetAdapters() {
if !ac.GetEnabled() {
continue
}
switch ac.GetType() {
case "mock":
// mock is always registered above.
case "ollama":
var cfg config.OllamaConf
if m := ac.GetOllama(); m != nil {
cfg = ollamaConfFromProto(m)
} else {
cfg = ollamaConfFromStruct(ac.GetSettings())
}
reg.Register(ollama.New(cfg, logger))
case "vllm":
var cfg config.VllmConf
if m := ac.GetVllm(); m != nil {
cfg = vllmConfFromProto(m)
} else {
cfg = vllmConfFromStruct(ac.GetSettings())
}
reg.Register(vllm.New(cfg, logger))
case "cli":
var cfg config.CLIConf
if m := ac.GetCli(); m != nil {
cfg = cliConfFromProto(m)
} else {
cfg = cliConfFromStruct(ac.GetSettings())
}
reg.Register(cli.New(cfg, logger))
default:
return nil, fmt.Errorf("adapters: unknown adapter type %q", ac.GetType())
}
}
return reg, nil
}
func ollamaConfFromProto(m *iop.OllamaAdapterConfig) config.OllamaConf {
return config.OllamaConf{Enabled: true, BaseURL: m.GetBaseUrl()}
}
func vllmConfFromProto(m *iop.VllmAdapterConfig) config.VllmConf {
return config.VllmConf{Enabled: true, Endpoint: m.GetEndpoint()}
}
func cliConfFromProto(m *iop.CLIAdapterConfig) config.CLIConf {
cfg := config.CLIConf{Enabled: true, Profiles: make(map[string]config.CLIProfileConf)}
for name, p := range m.GetProfiles() {
cfg.Profiles[name] = cliProfileFromProto(p)
}
return cfg
}
func cliProfileFromProto(p *iop.CLIProfileConfig) config.CLIProfileConf {
if p == nil {
return config.CLIProfileConf{}
}
prof := config.CLIProfileConf{
Command: p.GetCommand(),
Args: append([]string(nil), p.GetArgs()...),
Env: append([]string(nil), p.GetEnv()...),
Persistent: p.GetPersistent(),
Terminal: p.GetTerminal(),
ResponseIdleTimeoutMS: int(p.GetResponseIdleTimeoutMs()),
StartupIdleTimeoutMS: int(p.GetStartupIdleTimeoutMs()),
OutputFormat: p.GetOutputFormat(),
Mode: p.GetMode(),
ResumeArgs: append([]string(nil), p.GetResumeArgs()...),
}
if marker := p.GetCompletionMarker(); marker != nil {
prof.CompletionMarker = config.CompletionMarkerConf{
Line: marker.GetLine(),
Regex: marker.GetRegex(),
}
}
return prof
}
// Legacy structpb fallback paths for payloads from older edge versions
// that populate AdapterConfig.settings instead of the typed oneof.
func ollamaConfFromStruct(s *structpb.Struct) config.OllamaConf {
cfg := config.OllamaConf{Enabled: true}
if s == nil {
return cfg
}
m := s.AsMap()
if v, ok := m["base_url"].(string); ok {
cfg.BaseURL = v
}
return cfg
}
func vllmConfFromStruct(s *structpb.Struct) config.VllmConf {
cfg := config.VllmConf{Enabled: true}
if s == nil {
return cfg
}
m := s.AsMap()
if v, ok := m["endpoint"].(string); ok {
cfg.Endpoint = v
}
return cfg
}
func cliConfFromStruct(s *structpb.Struct) config.CLIConf {
cfg := config.CLIConf{Enabled: true, Profiles: make(map[string]config.CLIProfileConf)}
if s == nil {
return cfg
}
m := s.AsMap()
profiles, ok := m["profiles"].(map[string]any)
if !ok {
return cfg
}
for name, pAny := range profiles {
p, ok := pAny.(map[string]any)
if !ok {
continue
}
prof := config.CLIProfileConf{}
if v, ok := p["command"].(string); ok {
prof.Command = v
}
prof.Args = stringsFromAny(p["args"])
prof.Env = stringsFromAny(p["env"])
if v, ok := boolFromAny(p["persistent"]); ok {
prof.Persistent = v
}
if v, ok := boolFromAny(p["terminal"]); ok {
prof.Terminal = v
}
if v, ok := intFromAny(p["response_idle_timeout_ms"]); ok {
prof.ResponseIdleTimeoutMS = v
}
if v, ok := intFromAny(p["startup_idle_timeout_ms"]); ok {
prof.StartupIdleTimeoutMS = v
}
if v, ok := p["output_format"].(string); ok {
prof.OutputFormat = v
}
if v, ok := p["mode"].(string); ok {
prof.Mode = v
}
prof.ResumeArgs = stringsFromAny(p["resume_args"])
if marker, ok := p["completion_marker"].(map[string]any); ok {
if v, ok := marker["line"].(string); ok {
prof.CompletionMarker.Line = v
}
if v, ok := marker["regex"].(string); ok {
prof.CompletionMarker.Regex = v
}
}
cfg.Profiles[name] = prof
}
return cfg
}
func stringsFromAny(v any) []string {
arr, ok := v.([]any)
if !ok {
return nil
}
out := make([]string, 0, len(arr))
for _, a := range arr {
if s, ok := a.(string); ok {
out = append(out, s)
}
}
return out
}
func boolFromAny(v any) (bool, bool) {
if b, ok := v.(bool); ok {
return b, true
}
return false, false
}
func intFromAny(v any) (int, bool) {
if f, ok := v.(float64); ok {
return int(f), true
}
if i, ok := v.(int); ok {
return i, true
}
return 0, false
}