터미널 세션 core와 CLI mode executor 경계를 분리해 후속 remote terminal bridge가 재사용할 내부 실행 기반을 만든다. adapter 기본값과 vLLM experimental surface도 명시해 production 실행 경로가 mock fallback이나 미구현 실행으로 숨지 않게 한다.
341 lines
9.1 KiB
Go
341 lines
9.1 KiB
Go
package adapters
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
iop "iop/proto/gen/iop"
|
|
)
|
|
|
|
func TestCLIConfFromProto_ProfileRuntimeOptions(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"claude": {
|
|
Command: "claude",
|
|
Args: []string{"-p"},
|
|
OutputFormat: "claude-json",
|
|
},
|
|
},
|
|
}
|
|
|
|
cfg := cliConfFromProto(m)
|
|
|
|
prof, ok := cfg.Profiles["claude"]
|
|
if !ok {
|
|
t.Fatal("expected claude profile")
|
|
}
|
|
if prof.Command != "claude" {
|
|
t.Fatalf("command: got %q", prof.Command)
|
|
}
|
|
if len(prof.Args) != 1 || prof.Args[0] != "-p" {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
if prof.OutputFormat != "claude-json" {
|
|
t.Fatalf("output_format: got %q", prof.OutputFormat)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_ClaudeHeadlessBypassProfile(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"claude": {
|
|
Command: "claude",
|
|
Args: []string{"-p", "--dangerously-skip-permissions"},
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof, ok := cfg.Profiles["claude"]
|
|
if !ok {
|
|
t.Fatal("expected claude profile")
|
|
}
|
|
if len(prof.Args) != 2 || prof.Args[0] != "-p" || prof.Args[1] != "--dangerously-skip-permissions" {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
if prof.Persistent || prof.Terminal {
|
|
t.Fatalf("expected persistent/terminal=false")
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_AntigravityHeadlessProfile(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"antigravity": {
|
|
Command: "agy",
|
|
Args: []string{"--dangerously-skip-permissions", "--print"},
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof, ok := cfg.Profiles["antigravity"]
|
|
if !ok {
|
|
t.Fatal("expected antigravity profile")
|
|
}
|
|
if len(prof.Args) != 2 || prof.Args[0] != "--dangerously-skip-permissions" || prof.Args[1] != "--print" {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_ClineConfigProfile(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"cline-dgx": {
|
|
Command: "/config/.npm-global/bin/cline",
|
|
Args: []string{"-y", "--json", "--config", "/config/.cline/profiles/ollama-dgx"},
|
|
OutputFormat: "cline-json",
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof, ok := cfg.Profiles["cline-dgx"]
|
|
if !ok {
|
|
t.Fatal("expected cline-dgx profile")
|
|
}
|
|
if prof.Command != "/config/.npm-global/bin/cline" {
|
|
t.Fatalf("command: got %q", prof.Command)
|
|
}
|
|
if len(prof.Args) != 4 {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
if prof.OutputFormat != "cline-json" {
|
|
t.Fatalf("output_format: got %q", prof.OutputFormat)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_ProfileCompletionMarker(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"codex": {
|
|
Command: "codex",
|
|
Persistent: true,
|
|
CompletionMarker: &iop.CLICompletionMarker{
|
|
Line: "[[DONE]]",
|
|
Regex: `^\[\[DONE\]\]$`,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof := cfg.Profiles["codex"]
|
|
if prof.CompletionMarker.Line != "[[DONE]]" {
|
|
t.Fatalf("line: got %q", prof.CompletionMarker.Line)
|
|
}
|
|
if prof.CompletionMarker.Regex != `^\[\[DONE\]\]$` {
|
|
t.Fatalf("regex: got %q", prof.CompletionMarker.Regex)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_PersistentAndIdleTimeouts(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"codex": {
|
|
Command: "codex",
|
|
Persistent: true,
|
|
ResponseIdleTimeoutMs: 1500,
|
|
StartupIdleTimeoutMs: 300,
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof := cfg.Profiles["codex"]
|
|
if !prof.Persistent {
|
|
t.Fatal("persistent")
|
|
}
|
|
if prof.ResponseIdleTimeoutMS != 1500 {
|
|
t.Fatalf("response_idle_timeout_ms: got %d", prof.ResponseIdleTimeoutMS)
|
|
}
|
|
if prof.StartupIdleTimeoutMS != 300 {
|
|
t.Fatalf("startup_idle_timeout_ms: got %d", prof.StartupIdleTimeoutMS)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_NilSettings(t *testing.T) {
|
|
cfg := cliConfFromProto(&iop.CLIAdapterConfig{})
|
|
if !cfg.Enabled {
|
|
t.Fatal("expected enabled=true")
|
|
}
|
|
if cfg.Profiles == nil {
|
|
t.Fatal("profiles map should be initialized")
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromProto_OpencodeSSEMode(t *testing.T) {
|
|
m := &iop.CLIAdapterConfig{
|
|
Profiles: map[string]*iop.CLIProfileConfig{
|
|
"opencode": {
|
|
Command: "/usr/local/bin/opencode",
|
|
Args: []string{"--model", "m1", "--dangerously-skip-permissions"},
|
|
Mode: "opencode-sse",
|
|
},
|
|
},
|
|
}
|
|
cfg := cliConfFromProto(m)
|
|
prof, ok := cfg.Profiles["opencode"]
|
|
if !ok {
|
|
t.Fatal("expected opencode profile")
|
|
}
|
|
if prof.Mode != "opencode-sse" {
|
|
t.Errorf("mode: got %q", prof.Mode)
|
|
}
|
|
if len(prof.Args) != 3 {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromStruct_OpencodeSSEModeLegacy(t *testing.T) {
|
|
st, err := structpb.NewStruct(map[string]any{
|
|
"profiles": map[string]any{
|
|
"opencode": map[string]any{
|
|
"command": "/usr/local/bin/opencode",
|
|
"args": []any{"--model", "m1"},
|
|
"mode": "opencode-sse",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("structpb: %v", err)
|
|
}
|
|
cfg := cliConfFromStruct(st)
|
|
prof, ok := cfg.Profiles["opencode"]
|
|
if !ok {
|
|
t.Fatal("expected opencode profile")
|
|
}
|
|
if prof.Mode != "opencode-sse" {
|
|
t.Errorf("mode: got %q", prof.Mode)
|
|
}
|
|
}
|
|
|
|
// Legacy structpb fallback regression tests — these exercise the path taken
|
|
// when an older edge sends AdapterConfig.settings instead of the typed oneof.
|
|
|
|
func TestOllamaConfFromStruct_LegacyPayload(t *testing.T) {
|
|
st, err := structpb.NewStruct(map[string]any{
|
|
"base_url": "http://legacy:11434",
|
|
"context_size": float64(262144),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("structpb: %v", err)
|
|
}
|
|
cfg := ollamaConfFromStruct(st)
|
|
if !cfg.Enabled {
|
|
t.Fatal("expected enabled=true")
|
|
}
|
|
if cfg.BaseURL != "http://legacy:11434" {
|
|
t.Fatalf("base_url: got %q", cfg.BaseURL)
|
|
}
|
|
if cfg.ContextSize != 262144 {
|
|
t.Fatalf("context_size: got %d", cfg.ContextSize)
|
|
}
|
|
}
|
|
|
|
func TestVllmConfFromStruct_LegacyPayload(t *testing.T) {
|
|
st, err := structpb.NewStruct(map[string]any{"endpoint": "http://legacy:8000"})
|
|
if err != nil {
|
|
t.Fatalf("structpb: %v", err)
|
|
}
|
|
cfg := vllmConfFromStruct(st)
|
|
if !cfg.Enabled {
|
|
t.Fatal("expected enabled=true")
|
|
}
|
|
if cfg.Endpoint != "http://legacy:8000" {
|
|
t.Fatalf("endpoint: got %q", cfg.Endpoint)
|
|
}
|
|
}
|
|
|
|
func TestCLIConfFromStruct_LegacyPayload(t *testing.T) {
|
|
st, err := structpb.NewStruct(map[string]any{
|
|
"profiles": map[string]any{
|
|
"codex": map[string]any{
|
|
"command": "codex",
|
|
"args": []any{"--legacy"},
|
|
"env": []any{"K=V"},
|
|
"persistent": true,
|
|
"terminal": false,
|
|
"response_idle_timeout_ms": 1500,
|
|
"startup_idle_timeout_ms": 300,
|
|
"output_format": "codex-json",
|
|
"completion_marker": map[string]any{
|
|
"line": "[[DONE]]",
|
|
"regex": `^\[\[DONE\]\]$`,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("structpb: %v", err)
|
|
}
|
|
cfg := cliConfFromStruct(st)
|
|
prof, ok := cfg.Profiles["codex"]
|
|
if !ok {
|
|
t.Fatal("expected codex profile")
|
|
}
|
|
if prof.Command != "codex" {
|
|
t.Fatalf("command: got %q", prof.Command)
|
|
}
|
|
if len(prof.Args) != 1 || prof.Args[0] != "--legacy" {
|
|
t.Fatalf("args: got %#v", prof.Args)
|
|
}
|
|
if len(prof.Env) != 1 || prof.Env[0] != "K=V" {
|
|
t.Fatalf("env: got %#v", prof.Env)
|
|
}
|
|
if !prof.Persistent {
|
|
t.Fatal("persistent")
|
|
}
|
|
if prof.ResponseIdleTimeoutMS != 1500 {
|
|
t.Fatalf("response_idle_timeout_ms: got %d", prof.ResponseIdleTimeoutMS)
|
|
}
|
|
if prof.StartupIdleTimeoutMS != 300 {
|
|
t.Fatalf("startup_idle_timeout_ms: got %d", prof.StartupIdleTimeoutMS)
|
|
}
|
|
if prof.OutputFormat != "codex-json" {
|
|
t.Fatalf("output_format: got %q", prof.OutputFormat)
|
|
}
|
|
if prof.CompletionMarker.Line != "[[DONE]]" || prof.CompletionMarker.Regex != `^\[\[DONE\]\]$` {
|
|
t.Fatalf("completion_marker: got %+v", prof.CompletionMarker)
|
|
}
|
|
}
|
|
|
|
// End-to-end fallback through BuildFromPayload: oneof empty, settings populated.
|
|
func TestBuildFromPayload_LegacySettingsFallback(t *testing.T) {
|
|
ollamaSt, _ := structpb.NewStruct(map[string]any{"base_url": "http://legacy:11434"})
|
|
cliSt, _ := structpb.NewStruct(map[string]any{
|
|
"profiles": map[string]any{
|
|
"codex": map[string]any{
|
|
"command": "codex",
|
|
"args": []any{"--legacy"},
|
|
},
|
|
},
|
|
})
|
|
payload := &iop.NodeConfigPayload{
|
|
Adapters: []*iop.AdapterConfig{
|
|
{Type: "ollama", Enabled: true, Settings: ollamaSt},
|
|
{Type: "cli", Enabled: true, Settings: cliSt},
|
|
},
|
|
}
|
|
reg, err := BuildFromPayload(payload, nil)
|
|
if err != nil {
|
|
t.Fatalf("build: %v", err)
|
|
}
|
|
for _, name := range []string{"ollama", "cli"} {
|
|
if _, ok := reg.Get(name); !ok {
|
|
t.Fatalf("expected %s adapter registered from legacy settings", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildFromPayload_LegacyVllmSettingsRejected(t *testing.T) {
|
|
vllmSt, _ := structpb.NewStruct(map[string]any{"endpoint": "http://legacy:8000"})
|
|
_, err := BuildFromPayload(&iop.NodeConfigPayload{
|
|
Adapters: []*iop.AdapterConfig{
|
|
{Type: "vllm", Enabled: true, Settings: vllmSt},
|
|
},
|
|
}, nil)
|
|
if err == nil {
|
|
t.Fatal("expected vllm disabled error")
|
|
}
|
|
if !strings.Contains(err.Error(), "vllm adapter is experimental and disabled") {
|
|
t.Fatalf("expected vllm disabled error, got %v", err)
|
|
}
|
|
}
|