터미널 세션 core와 CLI mode executor 경계를 분리해 후속 remote terminal bridge가 재사용할 내부 실행 기반을 만든다. adapter 기본값과 vLLM experimental surface도 명시해 production 실행 경로가 mock fallback이나 미구현 실행으로 숨지 않게 한다.
232 lines
5.6 KiB
Go
232 lines
5.6 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/packages/go/config"
|
|
iop "iop/proto/gen/iop"
|
|
)
|
|
|
|
const vllmDisabledError = "adapters: vllm adapter is experimental and disabled until streaming execution is implemented"
|
|
|
|
// BuildFromPayload creates a Registry from a NodeConfigPayload received from edge.
|
|
func BuildFromPayload(payload *iop.NodeConfigPayload, logger *zap.Logger) (*Registry, error) {
|
|
reg := NewRegistry()
|
|
|
|
for _, ac := range payload.GetAdapters() {
|
|
if !ac.GetEnabled() {
|
|
continue
|
|
}
|
|
switch ac.GetType() {
|
|
case "mock":
|
|
reg.Register(mock.New(logger))
|
|
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":
|
|
return nil, fmt.Errorf(vllmDisabledError)
|
|
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(),
|
|
ContextSize: int(m.GetContextSize()),
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
if v, ok := intSetting(m, "context_size"); ok {
|
|
cfg.ContextSize = v
|
|
} else if v, ok := intSetting(m, "num_ctx"); ok {
|
|
cfg.ContextSize = v
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func intSetting(m map[string]any, key string) (int, bool) {
|
|
v, ok := m[key]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
switch n := v.(type) {
|
|
case int:
|
|
return n, true
|
|
case int32:
|
|
return int(n), true
|
|
case int64:
|
|
return int(n), true
|
|
case float64:
|
|
return int(n), true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|