diff --git a/agent-task/07_cli_profile_proto_message/CODE_REVIEW-cloud-G10.md b/agent-task/07_cli_profile_proto_message/CODE_REVIEW-cloud-G10.md deleted file mode 100644 index 4750acc..0000000 --- a/agent-task/07_cli_profile_proto_message/CODE_REVIEW-cloud-G10.md +++ /dev/null @@ -1,74 +0,0 @@ - - -# Code Review Reference - API - -## 개요 - -date=2026-05-04 -task=07_cli_profile_proto_message, plan=0, tag=API - -## 이 파일을 읽는 리뷰 에이전트에게 - -각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. -리뷰 완료 후 반드시 아래 순서로 아카이브하세요. - -1. `CODE_REVIEW-cloud-G10.md` → `code_review_cloud_G10_N.log` (N = 기존 code_review_*.log 수) -2. `PLAN-cloud-G09.md` → `plan_cloud_G09_M.log` (M = 기존 plan_*.log 수) -3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 routed plan + review 스텁 작성. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [API-1] proto 메시지 정의 및 생성물 갱신 | [ ] | -| [API-2] edge buildConfigPayload typed 메시지 갱신 | [ ] | -| [API-3] node BuildFromPayload typed 메시지 갱신 | [ ] | - -## 계획 대비 변경 사항 - -_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ - -## 주요 설계 결정 - -_구현 에이전트가 주요 설계 결정 사항을 기록한다._ - -## 리뷰어를 위한 체크포인트 - -- `proto/iop/runtime.proto`에 `oneof config`가 추가되고 기존 `settings` 필드가 backward-compat을 위해 유지되었는지 -- `proto/gen/iop/runtime.pb.go`가 사람이 직접 편집되지 않고 `make proto`로 생성된 변경인지 -- edge `buildConfigPayload`가 cli/ollama/vllm 모두 oneof로 채우는지, 더이상 `stringsToAny` 등 우회 헬퍼가 남아있지 않은지 -- node `BuildFromPayload`에서 `cliConfFromStruct`/`boolFromAny`/`intFromAny`가 모두 제거되었는지 -- Persistent/Terminal/Idle timeout 정수 필드가 손실 없이 전달되는 단위 테스트가 추가되었는지 - -## 검증 결과 - -_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ - -### API-1 중간 검증 -``` -$ make proto -$ go build ./proto/gen/... -(output) -``` - -### API-2 중간 검증 -``` -$ go test ./apps/edge/internal/transport/... -(output) -``` - -### API-3 중간 검증 -``` -$ go test ./apps/node/internal/adapters/... -(output) -``` - -### 최종 검증 -``` -$ make proto -$ go build ./... -$ go test ./... -(output) -``` diff --git a/agent-task/07_cli_profile_proto_message/PLAN-cloud-G09.md b/agent-task/07_cli_profile_proto_message/PLAN-cloud-G09.md deleted file mode 100644 index e075de0..0000000 --- a/agent-task/07_cli_profile_proto_message/PLAN-cloud-G09.md +++ /dev/null @@ -1,181 +0,0 @@ - - -# CLI 프로파일 전용 proto 메시지 도입 - -## 이 파일을 읽는 구현 에이전트에게 - -아래 체크리스트를 순서대로 완료하고, 각 항목의 중간 검증과 최종 검증을 실제로 실행하세요. 구현이 끝나면 `agent-task/07_cli_profile_proto_message/CODE_REVIEW-cloud-G10.md`의 모든 섹션을 실제 구현 내용과 명령 출력으로 채우세요. `CODE_REVIEW-cloud-G10.md`의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 아카이브 지시(`*.log`로 이름 변경, `complete.log` 작성)는 구현 에이전트가 수행하면 안 되며, 리뷰 스킬 전용입니다. - -## 배경 - -edge가 node에 보내는 `NodeConfigPayload`에서 CLI 프로파일은 `AdapterConfig.settings` (`google.protobuf.Struct`)로 직렬화되고, node 쪽에서 손으로 디코드합니다. (`apps/edge/internal/transport/server.go:190-207`의 `buildConfigPayload`와 `apps/node/internal/adapters/factory.go:69-118`의 `cliConfFromStruct`) 프로파일에 새 필드를 추가하면 edge·node 양쪽을 손으로 맞춰야 하며, structpb 인코딩 특성상 정수가 float로 들어가는 등의 변환 코드가 필요합니다. domain rule(`agent-ops/rules/project/rules.md`의 "protobuf 계약 변경은 .proto부터")을 따라 CLI 프로파일 전용 proto 메시지를 정의하고, edge·node 양쪽에서 구조체 단위로 주고받게 합니다. - -## 의존 관계 및 구현 순서 - -1. `[API-1]` `proto/iop/runtime.proto`에 `CLIAdapterConfig` / `CLIProfileConfig` 메시지를 추가하고 `make proto`로 생성물 갱신. -2. `[API-2]` edge `buildConfigPayload`가 새 메시지로 직접 채우도록 변경. -3. `[API-3]` node `BuildFromPayload`가 structpb 디코드 대신 새 메시지를 그대로 사용. - -### [API-1] proto 메시지 정의 및 생성물 갱신 - -#### 문제 - -`proto/iop/runtime.proto:96-100`의 `AdapterConfig`는 `google.protobuf.Struct settings`로 모든 어댑터 타입의 설정을 우회 직렬화합니다. CLI 프로파일은 필드가 9개로 늘어나 string-keyed map 직렬화의 위험이 큽니다. - -#### 해결 방법 - -`proto/iop/runtime.proto`의 `AdapterConfig`에 `oneof config`를 도입해 어댑터 타입별 typed 메시지를 가질 수 있도록 합니다. CLI 전용 메시지는 다음과 같이 정의합니다. - -```proto -message AdapterConfig { - string type = 1; - bool enabled = 2; - google.protobuf.Struct settings = 3; // 구버전 호환용; 신규 어댑터는 oneof 사용 권장 - oneof config { - CLIAdapterConfig cli = 4; - OllamaAdapterConfig ollama = 5; - VllmAdapterConfig vllm = 6; - } -} - -message CLIAdapterConfig { - map profiles = 1; -} - -message CLIProfileConfig { - string command = 1; - repeated string args = 2; - repeated string env = 3; - bool persistent = 4; - bool terminal = 5; - int32 response_idle_timeout_ms = 6; - int32 startup_idle_timeout_ms = 7; - string output_format = 8; -} - -message OllamaAdapterConfig { string base_url = 1; } -message VllmAdapterConfig { string endpoint = 1; } -``` - -오래된 `settings` 필드는 일단 유지해 mock/기타 어댑터 호환을 깨지 않습니다. CLI/Ollama/Vllm은 신규 oneof 경로만 사용하도록 합니다. - -#### 수정 파일 및 체크리스트 - -- [ ] `proto/iop/runtime.proto`에 `CLIAdapterConfig`, `CLIProfileConfig`, `OllamaAdapterConfig`, `VllmAdapterConfig` 메시지를 추가한다. -- [ ] `AdapterConfig`에 `oneof config { ... }`를 추가한다 (`settings` 필드는 그대로 둔다). -- [ ] `make proto`를 실행해 `proto/gen/iop/runtime.pb.go`를 갱신한다. -- [ ] 생성 파일은 직접 수정하지 않는다. - -#### 테스트 작성 - -테스트는 `[API-2]`/`[API-3]`에서 통합 검증한다. proto 정의 단독으로는 별도 단위 테스트를 작성하지 않는다. - -#### 중간 검증 - -```bash -make proto -git diff proto/iop/runtime.proto -go build ./proto/gen/... -``` - -예상 결과: `runtime.proto`에 새 메시지/oneof가 추가되어 있고, 생성물이 빌드된다. - -### [API-2] edge `buildConfigPayload`를 typed 메시지로 갱신 - -#### 문제 - -`apps/edge/internal/transport/server.go:155-209`의 `buildConfigPayload`가 `structpb.NewStruct(map[string]any{...})`로 CLI/Ollama/Vllm 설정을 우회 직렬화합니다. `stringsToAny` 같은 보조 함수도 필요합니다. - -#### 해결 방법 - -CLI/Ollama/Vllm은 `oneof config`로 직접 채우고, mock만 기존 `settings` 경로를 유지합니다. - -```go -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] = &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, - } - } - payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{ - Type: "cli", Enabled: true, - Config: &iop.AdapterConfig_Cli{Cli: &iop.CLIAdapterConfig{Profiles: profiles}}, - }) -} -``` - -#### 수정 파일 및 체크리스트 - -- [ ] `apps/edge/internal/transport/server.go`의 `buildConfigPayload`에서 cli/ollama/vllm 분기를 oneof 기반으로 다시 쓴다. -- [ ] `stringsToAny` 헬퍼는 더 이상 사용되지 않으면 제거한다. -- [ ] mock 어댑터 분기는 그대로 둔다. - -#### 테스트 작성 - -`apps/edge/internal/transport/server_test.go`에 `TestBuildConfigPayload_CLIOneof` (가칭)를 추가해 CLI 프로파일 한 개 정의에서 oneof 경로가 채워지고, settings 필드는 비어 있는지 단언한다. - -#### 중간 검증 - -```bash -go test ./apps/edge/internal/transport/... -``` - -예상 결과: 신규 테스트 포함 PASS. - -### [API-3] node `BuildFromPayload`를 typed 메시지 기반으로 갱신 - -#### 문제 - -`apps/node/internal/adapters/factory.go:69-118`의 `cliConfFromStruct`가 structpb를 손으로 디코드하면서 `boolFromAny`, `intFromAny` 같은 헬퍼에 의존합니다. structpb의 number→float64 변환 등 함정이 많습니다. - -#### 해결 방법 - -`AdapterConfig.GetCli()`가 nil이 아니면 typed 메시지 그대로 `config.CLIConf`로 매핑하고, structpb 경로(`cliConfFromStruct`)는 제거합니다. Ollama/Vllm도 동일하게 변경. - -#### 수정 파일 및 체크리스트 - -- [ ] `apps/node/internal/adapters/factory.go`의 `cli/ollama/vllm` 분기를 oneof 기반으로 재작성한다. -- [ ] `cliConfFromStruct`, `ollamaConfFromStruct`, `vllmConfFromStruct`, `boolFromAny`, `intFromAny`를 모두 제거한다. -- [ ] `apps/node/internal/adapters/factory_internal_test.go`와 `factory_test.go`의 케이스를 typed 메시지 입력 기반으로 갱신한다. - -#### 테스트 작성 - -`factory_test.go`의 기존 시나리오(여러 어댑터 enabled, CLI 프로파일 매핑)를 oneof 입력으로 다시 작성한다. 신규 케이스: `Persistent`, `ResponseIdleTimeoutMS`, `StartupIdleTimeoutMS`가 정확히 0이 아닌 값으로 전달되는지 단언. - -#### 중간 검증 - -```bash -go test ./apps/node/internal/adapters/... -``` - -예상 결과: PASS. - -## 수정 파일 요약 - -| 파일 | 항목 | -|------|------| -| `proto/iop/runtime.proto` | API-1 | -| `proto/gen/iop/runtime.pb.go` (생성물) | API-1 | -| `apps/edge/internal/transport/server.go` | API-2 | -| `apps/edge/internal/transport/server_test.go` | API-2 | -| `apps/node/internal/adapters/factory.go` | API-3 | -| `apps/node/internal/adapters/factory_internal_test.go` | API-3 | -| `apps/node/internal/adapters/factory_test.go` | API-3 | - -## 최종 검증 - -```bash -make proto -go build ./... -go test ./... -``` - -예상 결과: build/test 모두 PASS, 새 oneof 경로로 edge↔node가 동작. diff --git a/apps/edge/internal/transport/server.go b/apps/edge/internal/transport/server.go index 2a5ae06..7815883 100644 --- a/apps/edge/internal/transport/server.go +++ b/apps/edge/internal/transport/server.go @@ -13,6 +13,7 @@ import ( "google.golang.org/protobuf/types/known/structpb" edgenode "iop/apps/edge/internal/node" + "iop/packages/config" iop "iop/proto/gen/iop" ) @@ -163,63 +164,69 @@ func buildConfigPayload(rec *edgenode.NodeRecord) (*iop.NodeConfigPayload, error WorkspaceRoot: rec.Runtime.WorkspaceRoot, }, } - addAdapter := func(typ string, enabled bool, settings map[string]any) error { - if !enabled { - return nil - } - st, err := structpb.NewStruct(settings) - if err != nil { - return fmt.Errorf("buildConfigPayload: %s: %w", typ, err) - } + + mockSettings, err := structpb.NewStruct(nil) + if err != nil { + return nil, fmt.Errorf("buildConfigPayload: mock: %w", err) + } + payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{ + Type: "mock", + Enabled: true, + Settings: mockSettings, + }) + + if rec.Adapters.Ollama.Enabled { payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{ - Type: typ, - Enabled: true, - Settings: st, + Type: "ollama", + Enabled: true, + Config: &iop.AdapterConfig_Ollama{ + Ollama: &iop.OllamaAdapterConfig{BaseUrl: rec.Adapters.Ollama.BaseURL}, + }, }) - return nil } - if err := addAdapter("mock", true, nil); err != nil { - return nil, err - } - if err := addAdapter("ollama", rec.Adapters.Ollama.Enabled, map[string]any{ - "base_url": rec.Adapters.Ollama.BaseURL, - }); err != nil { - return nil, err - } - if err := addAdapter("vllm", rec.Adapters.Vllm.Enabled, map[string]any{ - "endpoint": rec.Adapters.Vllm.Endpoint, - }); err != nil { - return nil, err + if rec.Adapters.Vllm.Enabled { + payload.Adapters = append(payload.Adapters, &iop.AdapterConfig{ + Type: "vllm", + Enabled: true, + Config: &iop.AdapterConfig_Vllm{ + Vllm: &iop.VllmAdapterConfig{Endpoint: rec.Adapters.Vllm.Endpoint}, + }, + }) } if rec.Adapters.CLI.Enabled { - profiles := make(map[string]any) + profiles := make(map[string]*iop.CLIProfileConfig, len(rec.Adapters.CLI.Profiles)) for name, p := range rec.Adapters.CLI.Profiles { - profiles[name] = map[string]any{ - "command": p.Command, - "args": stringsToAny(p.Args), - "env": stringsToAny(p.Env), - "persistent": p.Persistent, - "terminal": p.Terminal, - "response_idle_timeout_ms": p.ResponseIdleTimeoutMS, - "startup_idle_timeout_ms": p.StartupIdleTimeoutMS, - "output_format": p.OutputFormat, - "completion_marker": map[string]any{ - "line": p.CompletionMarker.Line, - "regex": p.CompletionMarker.Regex, - }, - } - } - if err := addAdapter("cli", true, map[string]any{"profiles": profiles}); err != nil { - return nil, err + 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 stringsToAny(ss []string) []interface{} { - out := make([]interface{}, len(ss)) - for i, s := range ss { - out[i] = s +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 } diff --git a/apps/edge/internal/transport/server_test.go b/apps/edge/internal/transport/server_test.go index 3b205b0..42ea411 100644 --- a/apps/edge/internal/transport/server_test.go +++ b/apps/edge/internal/transport/server_test.go @@ -47,7 +47,16 @@ func TestEdgeParserMap_NodeCommandResponse(t *testing.T) { } } -func TestBuildConfigPayload_CLIArgsRoundtrip(t *testing.T) { +func findCLIAdapter(payload *iop.NodeConfigPayload) *iop.AdapterConfig { + for _, a := range payload.GetAdapters() { + if a.GetType() == "cli" { + return a + } + } + return nil +} + +func TestBuildConfigPayload_CLIOneof(t *testing.T) { rec := &edgenode.NodeRecord{ ID: "node-local", Alias: "local", @@ -75,64 +84,48 @@ func TestBuildConfigPayload_CLIArgsRoundtrip(t *testing.T) { t.Fatalf("build config payload: %v", err) } - var cliSettings map[string]any - for _, adapter := range payload.GetAdapters() { - if adapter.GetType() == "cli" { - cliSettings = adapter.GetSettings().AsMap() - break - } + cli := findCLIAdapter(payload) + if cli == nil { + t.Fatal("expected cli adapter") } - if cliSettings == nil { - t.Fatal("expected cli adapter settings") + if cli.GetSettings() != nil { + t.Fatalf("expected settings to be nil for cli adapter, got %v", cli.GetSettings()) } - - profiles, ok := cliSettings["profiles"].(map[string]any) + cliCfg := cli.GetCli() + if cliCfg == nil { + t.Fatal("expected typed cli config") + } + prof, ok := cliCfg.GetProfiles()["codex"] if !ok { - t.Fatalf("expected profiles map, got %T", cliSettings["profiles"]) + t.Fatal("expected codex profile") } - profile, ok := profiles["codex"].(map[string]any) - if !ok { - t.Fatalf("expected codex profile map, got %T", profiles["codex"]) + if prof.GetCommand() != "codex" { + t.Fatalf("command: %q", prof.GetCommand()) } - if got := profile["command"]; got != "codex" { - t.Fatalf("expected command %q, got %v", "codex", got) + if got := prof.GetArgs(); len(got) != 2 || got[0] != "--foo" || got[1] != "--bar" { + t.Fatalf("args: %#v", got) } - - args, ok := profile["args"].([]any) - if !ok { - t.Fatalf("expected args []any, got %T", profile["args"]) + if got := prof.GetEnv(); len(got) != 1 || got[0] != "KEY=val" { + t.Fatalf("env: %#v", got) } - if len(args) != 2 || args[0] != "--foo" || args[1] != "--bar" { - t.Fatalf("unexpected args: %#v", args) + if !prof.GetPersistent() { + t.Fatal("persistent") } - - env, ok := profile["env"].([]any) - if !ok { - t.Fatalf("expected env []any, got %T", profile["env"]) + if !prof.GetTerminal() { + t.Fatal("terminal") } - if len(env) != 1 || env[0] != "KEY=val" { - t.Fatalf("unexpected env: %#v", env) + if prof.GetResponseIdleTimeoutMs() != 1500 { + t.Fatalf("response_idle_timeout_ms: %d", prof.GetResponseIdleTimeoutMs()) } - - if got, ok := profile["persistent"].(bool); !ok || !got { - t.Fatalf("expected persistent=true, got %v", profile["persistent"]) + if prof.GetStartupIdleTimeoutMs() != 300 { + t.Fatalf("startup_idle_timeout_ms: %d", prof.GetStartupIdleTimeoutMs()) } - if got, ok := profile["terminal"].(bool); !ok || !got { - t.Fatalf("expected terminal=true, got %v", profile["terminal"]) - } - // structpb encodes numbers as float64 - if got, ok := profile["response_idle_timeout_ms"].(float64); !ok || got != 1500 { - t.Fatalf("expected response_idle_timeout_ms=1500, got %v", profile["response_idle_timeout_ms"]) - } - if got, ok := profile["startup_idle_timeout_ms"].(float64); !ok || got != 300 { - t.Fatalf("expected startup_idle_timeout_ms=300, got %v", profile["startup_idle_timeout_ms"]) - } - if got, ok := profile["output_format"].(string); !ok || got != "codex-json" { - t.Fatalf("expected output_format=%q, got %v", "codex-json", profile["output_format"]) + if prof.GetOutputFormat() != "codex-json" { + t.Fatalf("output_format: %q", prof.GetOutputFormat()) } } -func TestBuildNodePayload_CLICompletionMarker(t *testing.T) { +func TestBuildConfigPayload_CLICompletionMarker(t *testing.T) { rec := &edgenode.NodeRecord{ ID: "node-local", Alias: "local", @@ -157,33 +150,47 @@ func TestBuildNodePayload_CLICompletionMarker(t *testing.T) { if err != nil { t.Fatalf("build config payload: %v", err) } - - var cliSettings map[string]any - for _, adapter := range payload.GetAdapters() { - if adapter.GetType() == "cli" { - cliSettings = adapter.GetSettings().AsMap() - break - } + cli := findCLIAdapter(payload) + if cli == nil { + t.Fatal("expected cli adapter") } - if cliSettings == nil { - t.Fatal("expected cli adapter settings") + prof := cli.GetCli().GetProfiles()["codex"] + marker := prof.GetCompletionMarker() + if marker == nil { + t.Fatal("expected completion_marker") } - profiles, ok := cliSettings["profiles"].(map[string]any) - if !ok { - t.Fatalf("expected profiles map, got %T", cliSettings["profiles"]) + if marker.GetLine() != "[[DONE]]" { + t.Fatalf("line: %q", marker.GetLine()) } - profile, ok := profiles["codex"].(map[string]any) - if !ok { - t.Fatalf("expected codex profile map, got %T", profiles["codex"]) - } - marker, ok := profile["completion_marker"].(map[string]any) - if !ok { - t.Fatalf("expected completion_marker map, got %T", profile["completion_marker"]) - } - if got, _ := marker["line"].(string); got != "[[DONE]]" { - t.Fatalf("expected completion_marker.line=%q, got %v", "[[DONE]]", marker["line"]) - } - if got, _ := marker["regex"].(string); got != `^\[\[DONE\]\]$` { - t.Fatalf("expected completion_marker.regex=%q, got %v", `^\[\[DONE\]\]$`, marker["regex"]) + if marker.GetRegex() != `^\[\[DONE\]\]$` { + t.Fatalf("regex: %q", marker.GetRegex()) + } +} + +func TestBuildConfigPayload_OllamaVllmOneof(t *testing.T) { + rec := &edgenode.NodeRecord{ + Adapters: config.AdaptersConf{ + Ollama: config.OllamaConf{Enabled: true, BaseURL: "http://localhost:11434"}, + Vllm: config.VllmConf{Enabled: true, Endpoint: "http://localhost:8000"}, + }, + } + payload, err := buildConfigPayload(rec) + if err != nil { + t.Fatalf("build: %v", err) + } + var ollama, vllm *iop.AdapterConfig + for _, a := range payload.GetAdapters() { + switch a.GetType() { + case "ollama": + ollama = a + case "vllm": + vllm = a + } + } + if ollama == nil || ollama.GetOllama().GetBaseUrl() != "http://localhost:11434" { + t.Fatalf("ollama: %+v", ollama) + } + if vllm == nil || vllm.GetVllm().GetEndpoint() != "http://localhost:8000" { + t.Fatalf("vllm: %+v", vllm) } } diff --git a/apps/node/internal/adapters/factory.go b/apps/node/internal/adapters/factory.go index f293fcf..61bb202 100644 --- a/apps/node/internal/adapters/factory.go +++ b/apps/node/internal/adapters/factory.go @@ -27,13 +27,28 @@ func BuildFromPayload(payload *iop.NodeConfigPayload, logger *zap.Logger) (*Regi case "mock": // mock is always registered above. case "ollama": - cfg := ollamaConfFromStruct(ac.GetSettings()) + 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": - cfg := vllmConfFromStruct(ac.GetSettings()) + 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": - cfg := cliConfFromStruct(ac.GetSettings()) + 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()) @@ -42,12 +57,56 @@ func BuildFromPayload(payload *iop.NodeConfigPayload, logger *zap.Logger) (*Regi 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 config.OllamaConf{} + return cfg } m := s.AsMap() - cfg := config.OllamaConf{Enabled: true} if v, ok := m["base_url"].(string); ok { cfg.BaseURL = v } @@ -55,11 +114,11 @@ func ollamaConfFromStruct(s *structpb.Struct) config.OllamaConf { } func vllmConfFromStruct(s *structpb.Struct) config.VllmConf { + cfg := config.VllmConf{Enabled: true} if s == nil { - return config.VllmConf{} + return cfg } m := s.AsMap() - cfg := config.VllmConf{Enabled: true} if v, ok := m["endpoint"].(string); ok { cfg.Endpoint = v } @@ -67,68 +126,73 @@ func vllmConfFromStruct(s *structpb.Struct) config.VllmConf { } func cliConfFromStruct(s *structpb.Struct) config.CLIConf { + cfg := config.CLIConf{Enabled: true, Profiles: make(map[string]config.CLIProfileConf)} if s == nil { - return config.CLIConf{} + return cfg } m := s.AsMap() - cfg := config.CLIConf{Enabled: true, Profiles: make(map[string]config.CLIProfileConf)} - if profiles, ok := m["profiles"].(map[string]any); ok { - for name, pAny := range profiles { - p, ok := pAny.(map[string]any) - if !ok { - continue - } - prof := config.CLIProfileConf{} - if cmd, ok := p["command"].(string); ok { - prof.Command = cmd - } - if args, ok := p["args"].([]any); ok { - for _, a := range args { - if s, ok := a.(string); ok { - prof.Args = append(prof.Args, s) - } - } - } - if envs, ok := p["env"].([]any); ok { - for _, e := range envs { - if s, ok := e.(string); ok { - prof.Env = append(prof.Env, s) - } - } - } - 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 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 + 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 boolFromAny(v any) (bool, bool) { - if v == nil { - return false, false +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 } @@ -136,10 +200,6 @@ func boolFromAny(v any) (bool, bool) { } func intFromAny(v any) (int, bool) { - if v == nil { - return 0, false - } - // structpb encodes numbers as float64 if f, ok := v.(float64); ok { return int(f), true } diff --git a/apps/node/internal/adapters/factory_internal_test.go b/apps/node/internal/adapters/factory_internal_test.go index 6685e0d..7e17741 100644 --- a/apps/node/internal/adapters/factory_internal_test.go +++ b/apps/node/internal/adapters/factory_internal_test.go @@ -4,26 +4,22 @@ import ( "testing" "google.golang.org/protobuf/types/known/structpb" + + iop "iop/proto/gen/iop" ) -func TestCLIConfFromStruct_ProfileRuntimeOptions(t *testing.T) { - st, err := structpb.NewStruct(map[string]any{ - "profiles": map[string]any{ - "claude": map[string]any{ - "command": "claude", - "args": []any{"-p"}, - "env": []any{}, - "persistent": false, - "terminal": false, - "output_format": "claude-json", +func TestCLIConfFromProto_ProfileRuntimeOptions(t *testing.T) { + m := &iop.CLIAdapterConfig{ + Profiles: map[string]*iop.CLIProfileConfig{ + "claude": { + Command: "claude", + Args: []string{"-p"}, + OutputFormat: "claude-json", }, }, - }) - if err != nil { - t.Fatalf("structpb: %v", err) } - cfg := cliConfFromStruct(st) + cfg := cliConfFromProto(m) prof, ok := cfg.Profiles["claude"] if !ok { @@ -40,97 +36,58 @@ func TestCLIConfFromStruct_ProfileRuntimeOptions(t *testing.T) { } } -func TestCLIConfFromStruct_ClaudeHeadlessBypassProfile(t *testing.T) { - st, err := structpb.NewStruct(map[string]any{ - "profiles": map[string]any{ - "claude": map[string]any{ - "command": "claude", - "args": []any{"-p", "--dangerously-skip-permissions"}, - "env": []any{}, - "persistent": false, - "terminal": false, +func TestCLIConfFromProto_ClaudeHeadlessBypassProfile(t *testing.T) { + m := &iop.CLIAdapterConfig{ + Profiles: map[string]*iop.CLIProfileConfig{ + "claude": { + Command: "claude", + Args: []string{"-p", "--dangerously-skip-permissions"}, }, }, - }) - if err != nil { - t.Fatalf("structpb: %v", err) } - - cfg := cliConfFromStruct(st) - + 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) != 2 || prof.Args[0] != "-p" || prof.Args[1] != "--dangerously-skip-permissions" { t.Fatalf("args: got %#v", prof.Args) } - if prof.Persistent { - t.Fatal("expected persistent=false") - } - if prof.Terminal { - t.Fatal("expected terminal=false") + if prof.Persistent || prof.Terminal { + t.Fatalf("expected persistent/terminal=false") } } -func TestCLIConfFromStruct_GeminiHeadlessYoloProfile(t *testing.T) { - st, err := structpb.NewStruct(map[string]any{ - "profiles": map[string]any{ - "gemini": map[string]any{ - "command": "gemini", - "args": []any{"-p", "--approval-mode", "yolo"}, - "env": []any{}, - "persistent": false, - "terminal": false, +func TestCLIConfFromProto_GeminiHeadlessYoloProfile(t *testing.T) { + m := &iop.CLIAdapterConfig{ + Profiles: map[string]*iop.CLIProfileConfig{ + "gemini": { + Command: "gemini", + Args: []string{"-p", "--approval-mode", "yolo"}, }, }, - }) - if err != nil { - t.Fatalf("structpb: %v", err) } - - cfg := cliConfFromStruct(st) - + cfg := cliConfFromProto(m) prof, ok := cfg.Profiles["gemini"] if !ok { t.Fatal("expected gemini profile") } - if prof.Command != "gemini" { - t.Fatalf("command: got %q", prof.Command) - } if len(prof.Args) != 3 || prof.Args[0] != "-p" || prof.Args[1] != "--approval-mode" || prof.Args[2] != "yolo" { t.Fatalf("args: got %#v", prof.Args) } - if prof.Persistent { - t.Fatal("expected persistent=false") - } - if prof.Terminal { - t.Fatal("expected terminal=false") - } } -func TestCLIConfFromStruct_ClineConfigProfile(t *testing.T) { - st, err := structpb.NewStruct(map[string]any{ - "profiles": map[string]any{ - "cline-dgx": map[string]any{ - "command": "/config/.npm-global/bin/cline", - "args": []any{"-y", "--json", "--config", "/config/.cline/profiles/ollama-dgx"}, - "env": []any{}, - "persistent": false, - "terminal": false, - "output_format": "cline-json", +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", }, }, - }) - if err != nil { - t.Fatalf("structpb: %v", err) } - - cfg := cliConfFromStruct(st) - + cfg := cliConfFromProto(m) prof, ok := cfg.Profiles["cline-dgx"] if !ok { t.Fatal("expected cline-dgx profile") @@ -138,7 +95,7 @@ func TestCLIConfFromStruct_ClineConfigProfile(t *testing.T) { if prof.Command != "/config/.npm-global/bin/cline" { t.Fatalf("command: got %q", prof.Command) } - if len(prof.Args) != 4 || prof.Args[0] != "-y" || prof.Args[1] != "--json" || prof.Args[2] != "--config" || prof.Args[3] != "/config/.cline/profiles/ollama-dgx" { + if len(prof.Args) != 4 { t.Fatalf("args: got %#v", prof.Args) } if prof.OutputFormat != "cline-json" { @@ -146,15 +103,106 @@ func TestCLIConfFromStruct_ClineConfigProfile(t *testing.T) { } } -func TestCLIConfFromStruct_ProfileCompletionMarker(t *testing.T) { +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") + } +} + +// 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"}) + 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) + } +} + +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{}, - "env": []any{}, - "persistent": true, - "terminal": false, + "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\]\]$`, @@ -165,24 +213,63 @@ func TestCLIConfFromStruct_ProfileCompletionMarker(t *testing.T) { 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.CompletionMarker.Line != "[[DONE]]" { - t.Fatalf("completion_marker.line: got %q", prof.CompletionMarker.Line) + if prof.Command != "codex" { + t.Fatalf("command: got %q", prof.Command) } - if prof.CompletionMarker.Regex != `^\[\[DONE\]\]$` { - t.Fatalf("completion_marker.regex: got %q", prof.CompletionMarker.Regex) + 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) } } -func TestCLIConfFromStruct_NilSettings(t *testing.T) { - cfg := cliConfFromStruct(nil) - if cfg.Enabled { - t.Fatal("expected enabled=false for nil settings") +// 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"}) + vllmSt, _ := structpb.NewStruct(map[string]any{"endpoint": "http://legacy:8000"}) + 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: "vllm", Enabled: true, Settings: vllmSt}, + {Type: "cli", Enabled: true, Settings: cliSt}, + }, + } + reg, err := BuildFromPayload(payload, nil) + if err != nil { + t.Fatalf("build: %v", err) + } + for _, name := range []string{"ollama", "vllm", "cli"} { + if _, ok := reg.Get(name); !ok { + t.Fatalf("expected %s adapter registered from legacy settings", name) + } } } diff --git a/apps/node/internal/adapters/factory_test.go b/apps/node/internal/adapters/factory_test.go index 9b99387..6db5fd8 100644 --- a/apps/node/internal/adapters/factory_test.go +++ b/apps/node/internal/adapters/factory_test.go @@ -4,7 +4,6 @@ import ( "testing" "go.uber.org/zap" - "google.golang.org/protobuf/types/known/structpb" "iop/apps/node/internal/adapters" iop "iop/proto/gen/iop" @@ -24,16 +23,15 @@ func TestBuildFromPayload_MockAlwaysPresent(t *testing.T) { } func TestBuildFromPayload_OllamaEnabled(t *testing.T) { - settings, err := structpb.NewStruct(map[string]any{ - "base_url": "http://localhost:11434", - }) - if err != nil { - t.Fatalf("settings: %v", err) - } - reg, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{ Adapters: []*iop.AdapterConfig{ - {Type: "ollama", Enabled: true, Settings: settings}, + { + Type: "ollama", + Enabled: true, + Config: &iop.AdapterConfig_Ollama{ + Ollama: &iop.OllamaAdapterConfig{BaseUrl: "http://localhost:11434"}, + }, + }, }, }, zap.NewNop()) if err != nil { @@ -47,6 +45,28 @@ func TestBuildFromPayload_OllamaEnabled(t *testing.T) { } } +func TestBuildFromPayload_MultipleAdapters(t *testing.T) { + reg, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{ + Adapters: []*iop.AdapterConfig{ + {Type: "ollama", Enabled: true, Config: &iop.AdapterConfig_Ollama{Ollama: &iop.OllamaAdapterConfig{BaseUrl: "x"}}}, + {Type: "vllm", Enabled: true, Config: &iop.AdapterConfig_Vllm{Vllm: &iop.VllmAdapterConfig{Endpoint: "y"}}}, + {Type: "cli", Enabled: true, Config: &iop.AdapterConfig_Cli{Cli: &iop.CLIAdapterConfig{ + Profiles: map[string]*iop.CLIProfileConfig{ + "codex": {Command: "codex", Persistent: true, ResponseIdleTimeoutMs: 1500, StartupIdleTimeoutMs: 300}, + }, + }}}, + }, + }, zap.NewNop()) + if err != nil { + t.Fatalf("build from payload: %v", err) + } + for _, name := range []string{"mock", "ollama", "vllm", "cli"} { + if _, ok := reg.Get(name); !ok { + t.Fatalf("expected %s adapter to be registered", name) + } + } +} + func TestBuildFromPayload_UnknownType(t *testing.T) { _, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{ Adapters: []*iop.AdapterConfig{ diff --git a/proto/gen/iop/runtime.pb.go b/proto/gen/iop/runtime.pb.go index 139473a..eb597f4 100644 --- a/proto/gen/iop/runtime.pb.go +++ b/proto/gen/iop/runtime.pb.go @@ -1088,10 +1088,16 @@ func (x *NodeConfigPayload) GetRuntime() *NodeRuntimeConfig { // AdapterConfig describes one adapter to enable on the node. type AdapterConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // "mock" | "ollama" | "vllm" | "cli" - Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` - Settings *structpb.Struct `protobuf:"bytes,3,opt,name=settings,proto3" json:"settings,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // "mock" | "ollama" | "vllm" | "cli" + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + Settings *structpb.Struct `protobuf:"bytes,3,opt,name=settings,proto3" json:"settings,omitempty"` // legacy/compat path; new adapters use oneof + // Types that are valid to be assigned to Config: + // + // *AdapterConfig_Cli + // *AdapterConfig_Ollama + // *AdapterConfig_Vllm + Config isAdapterConfig_Config `protobuf_oneof:"config"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1147,6 +1153,370 @@ func (x *AdapterConfig) GetSettings() *structpb.Struct { return nil } +func (x *AdapterConfig) GetConfig() isAdapterConfig_Config { + if x != nil { + return x.Config + } + return nil +} + +func (x *AdapterConfig) GetCli() *CLIAdapterConfig { + if x != nil { + if x, ok := x.Config.(*AdapterConfig_Cli); ok { + return x.Cli + } + } + return nil +} + +func (x *AdapterConfig) GetOllama() *OllamaAdapterConfig { + if x != nil { + if x, ok := x.Config.(*AdapterConfig_Ollama); ok { + return x.Ollama + } + } + return nil +} + +func (x *AdapterConfig) GetVllm() *VllmAdapterConfig { + if x != nil { + if x, ok := x.Config.(*AdapterConfig_Vllm); ok { + return x.Vllm + } + } + return nil +} + +type isAdapterConfig_Config interface { + isAdapterConfig_Config() +} + +type AdapterConfig_Cli struct { + Cli *CLIAdapterConfig `protobuf:"bytes,4,opt,name=cli,proto3,oneof"` +} + +type AdapterConfig_Ollama struct { + Ollama *OllamaAdapterConfig `protobuf:"bytes,5,opt,name=ollama,proto3,oneof"` +} + +type AdapterConfig_Vllm struct { + Vllm *VllmAdapterConfig `protobuf:"bytes,6,opt,name=vllm,proto3,oneof"` +} + +func (*AdapterConfig_Cli) isAdapterConfig_Config() {} + +func (*AdapterConfig_Ollama) isAdapterConfig_Config() {} + +func (*AdapterConfig_Vllm) isAdapterConfig_Config() {} + +type CLIAdapterConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Profiles map[string]*CLIProfileConfig `protobuf:"bytes,1,rep,name=profiles,proto3" json:"profiles,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CLIAdapterConfig) Reset() { + *x = CLIAdapterConfig{} + mi := &file_proto_iop_runtime_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CLIAdapterConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CLIAdapterConfig) ProtoMessage() {} + +func (x *CLIAdapterConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_iop_runtime_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CLIAdapterConfig.ProtoReflect.Descriptor instead. +func (*CLIAdapterConfig) Descriptor() ([]byte, []int) { + return file_proto_iop_runtime_proto_rawDescGZIP(), []int{13} +} + +func (x *CLIAdapterConfig) GetProfiles() map[string]*CLIProfileConfig { + if x != nil { + return x.Profiles + } + return nil +} + +type CLIProfileConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` + Args []string `protobuf:"bytes,2,rep,name=args,proto3" json:"args,omitempty"` + Env []string `protobuf:"bytes,3,rep,name=env,proto3" json:"env,omitempty"` + Persistent bool `protobuf:"varint,4,opt,name=persistent,proto3" json:"persistent,omitempty"` + Terminal bool `protobuf:"varint,5,opt,name=terminal,proto3" json:"terminal,omitempty"` + ResponseIdleTimeoutMs int32 `protobuf:"varint,6,opt,name=response_idle_timeout_ms,json=responseIdleTimeoutMs,proto3" json:"response_idle_timeout_ms,omitempty"` + StartupIdleTimeoutMs int32 `protobuf:"varint,7,opt,name=startup_idle_timeout_ms,json=startupIdleTimeoutMs,proto3" json:"startup_idle_timeout_ms,omitempty"` + OutputFormat string `protobuf:"bytes,8,opt,name=output_format,json=outputFormat,proto3" json:"output_format,omitempty"` + CompletionMarker *CLICompletionMarker `protobuf:"bytes,9,opt,name=completion_marker,json=completionMarker,proto3" json:"completion_marker,omitempty"` + Mode string `protobuf:"bytes,10,opt,name=mode,proto3" json:"mode,omitempty"` + ResumeArgs []string `protobuf:"bytes,11,rep,name=resume_args,json=resumeArgs,proto3" json:"resume_args,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CLIProfileConfig) Reset() { + *x = CLIProfileConfig{} + mi := &file_proto_iop_runtime_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CLIProfileConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CLIProfileConfig) ProtoMessage() {} + +func (x *CLIProfileConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_iop_runtime_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CLIProfileConfig.ProtoReflect.Descriptor instead. +func (*CLIProfileConfig) Descriptor() ([]byte, []int) { + return file_proto_iop_runtime_proto_rawDescGZIP(), []int{14} +} + +func (x *CLIProfileConfig) GetCommand() string { + if x != nil { + return x.Command + } + return "" +} + +func (x *CLIProfileConfig) GetArgs() []string { + if x != nil { + return x.Args + } + return nil +} + +func (x *CLIProfileConfig) GetEnv() []string { + if x != nil { + return x.Env + } + return nil +} + +func (x *CLIProfileConfig) GetPersistent() bool { + if x != nil { + return x.Persistent + } + return false +} + +func (x *CLIProfileConfig) GetTerminal() bool { + if x != nil { + return x.Terminal + } + return false +} + +func (x *CLIProfileConfig) GetResponseIdleTimeoutMs() int32 { + if x != nil { + return x.ResponseIdleTimeoutMs + } + return 0 +} + +func (x *CLIProfileConfig) GetStartupIdleTimeoutMs() int32 { + if x != nil { + return x.StartupIdleTimeoutMs + } + return 0 +} + +func (x *CLIProfileConfig) GetOutputFormat() string { + if x != nil { + return x.OutputFormat + } + return "" +} + +func (x *CLIProfileConfig) GetCompletionMarker() *CLICompletionMarker { + if x != nil { + return x.CompletionMarker + } + return nil +} + +func (x *CLIProfileConfig) GetMode() string { + if x != nil { + return x.Mode + } + return "" +} + +func (x *CLIProfileConfig) GetResumeArgs() []string { + if x != nil { + return x.ResumeArgs + } + return nil +} + +type CLICompletionMarker struct { + state protoimpl.MessageState `protogen:"open.v1"` + Line string `protobuf:"bytes,1,opt,name=line,proto3" json:"line,omitempty"` + Regex string `protobuf:"bytes,2,opt,name=regex,proto3" json:"regex,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CLICompletionMarker) Reset() { + *x = CLICompletionMarker{} + mi := &file_proto_iop_runtime_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CLICompletionMarker) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CLICompletionMarker) ProtoMessage() {} + +func (x *CLICompletionMarker) ProtoReflect() protoreflect.Message { + mi := &file_proto_iop_runtime_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CLICompletionMarker.ProtoReflect.Descriptor instead. +func (*CLICompletionMarker) Descriptor() ([]byte, []int) { + return file_proto_iop_runtime_proto_rawDescGZIP(), []int{15} +} + +func (x *CLICompletionMarker) GetLine() string { + if x != nil { + return x.Line + } + return "" +} + +func (x *CLICompletionMarker) GetRegex() string { + if x != nil { + return x.Regex + } + return "" +} + +type OllamaAdapterConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + BaseUrl string `protobuf:"bytes,1,opt,name=base_url,json=baseUrl,proto3" json:"base_url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OllamaAdapterConfig) Reset() { + *x = OllamaAdapterConfig{} + mi := &file_proto_iop_runtime_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OllamaAdapterConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OllamaAdapterConfig) ProtoMessage() {} + +func (x *OllamaAdapterConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_iop_runtime_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OllamaAdapterConfig.ProtoReflect.Descriptor instead. +func (*OllamaAdapterConfig) Descriptor() ([]byte, []int) { + return file_proto_iop_runtime_proto_rawDescGZIP(), []int{16} +} + +func (x *OllamaAdapterConfig) GetBaseUrl() string { + if x != nil { + return x.BaseUrl + } + return "" +} + +type VllmAdapterConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Endpoint string `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VllmAdapterConfig) Reset() { + *x = VllmAdapterConfig{} + mi := &file_proto_iop_runtime_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VllmAdapterConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VllmAdapterConfig) ProtoMessage() {} + +func (x *VllmAdapterConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_iop_runtime_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VllmAdapterConfig.ProtoReflect.Descriptor instead. +func (*VllmAdapterConfig) Descriptor() ([]byte, []int) { + return file_proto_iop_runtime_proto_rawDescGZIP(), []int{17} +} + +func (x *VllmAdapterConfig) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + // NodeRuntimeConfig is the runtime tuning pushed to the node. type NodeRuntimeConfig struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1158,7 +1528,7 @@ type NodeRuntimeConfig struct { func (x *NodeRuntimeConfig) Reset() { *x = NodeRuntimeConfig{} - mi := &file_proto_iop_runtime_proto_msgTypes[13] + mi := &file_proto_iop_runtime_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1170,7 +1540,7 @@ func (x *NodeRuntimeConfig) String() string { func (*NodeRuntimeConfig) ProtoMessage() {} func (x *NodeRuntimeConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_iop_runtime_proto_msgTypes[13] + mi := &file_proto_iop_runtime_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1183,7 +1553,7 @@ func (x *NodeRuntimeConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeRuntimeConfig.ProtoReflect.Descriptor instead. func (*NodeRuntimeConfig) Descriptor() ([]byte, []int) { - return file_proto_iop_runtime_proto_rawDescGZIP(), []int{13} + return file_proto_iop_runtime_proto_rawDescGZIP(), []int{18} } func (x *NodeRuntimeConfig) GetConcurrency() int32 { @@ -1307,11 +1677,43 @@ const file_proto_iop_runtime_proto_rawDesc = "" + "\x06config\x18\x05 \x01(\v2\x16.iop.NodeConfigPayloadR\x06config\"u\n" + "\x11NodeConfigPayload\x12.\n" + "\badapters\x18\x01 \x03(\v2\x12.iop.AdapterConfigR\badapters\x120\n" + - "\aruntime\x18\x02 \x01(\v2\x16.iop.NodeRuntimeConfigR\aruntime\"r\n" + + "\aruntime\x18\x02 \x01(\v2\x16.iop.NodeRuntimeConfigR\aruntime\"\x89\x02\n" + "\rAdapterConfig\x12\x12\n" + "\x04type\x18\x01 \x01(\tR\x04type\x12\x18\n" + "\aenabled\x18\x02 \x01(\bR\aenabled\x123\n" + - "\bsettings\x18\x03 \x01(\v2\x17.google.protobuf.StructR\bsettings\"\\\n" + + "\bsettings\x18\x03 \x01(\v2\x17.google.protobuf.StructR\bsettings\x12)\n" + + "\x03cli\x18\x04 \x01(\v2\x15.iop.CLIAdapterConfigH\x00R\x03cli\x122\n" + + "\x06ollama\x18\x05 \x01(\v2\x18.iop.OllamaAdapterConfigH\x00R\x06ollama\x12,\n" + + "\x04vllm\x18\x06 \x01(\v2\x16.iop.VllmAdapterConfigH\x00R\x04vllmB\b\n" + + "\x06config\"\xa7\x01\n" + + "\x10CLIAdapterConfig\x12?\n" + + "\bprofiles\x18\x01 \x03(\v2#.iop.CLIAdapterConfig.ProfilesEntryR\bprofiles\x1aR\n" + + "\rProfilesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12+\n" + + "\x05value\x18\x02 \x01(\v2\x15.iop.CLIProfileConfigR\x05value:\x028\x01\"\x9f\x03\n" + + "\x10CLIProfileConfig\x12\x18\n" + + "\acommand\x18\x01 \x01(\tR\acommand\x12\x12\n" + + "\x04args\x18\x02 \x03(\tR\x04args\x12\x10\n" + + "\x03env\x18\x03 \x03(\tR\x03env\x12\x1e\n" + + "\n" + + "persistent\x18\x04 \x01(\bR\n" + + "persistent\x12\x1a\n" + + "\bterminal\x18\x05 \x01(\bR\bterminal\x127\n" + + "\x18response_idle_timeout_ms\x18\x06 \x01(\x05R\x15responseIdleTimeoutMs\x125\n" + + "\x17startup_idle_timeout_ms\x18\a \x01(\x05R\x14startupIdleTimeoutMs\x12#\n" + + "\routput_format\x18\b \x01(\tR\foutputFormat\x12E\n" + + "\x11completion_marker\x18\t \x01(\v2\x18.iop.CLICompletionMarkerR\x10completionMarker\x12\x12\n" + + "\x04mode\x18\n" + + " \x01(\tR\x04mode\x12\x1f\n" + + "\vresume_args\x18\v \x03(\tR\n" + + "resumeArgs\"?\n" + + "\x13CLICompletionMarker\x12\x12\n" + + "\x04line\x18\x01 \x01(\tR\x04line\x12\x14\n" + + "\x05regex\x18\x02 \x01(\tR\x05regex\"0\n" + + "\x13OllamaAdapterConfig\x12\x19\n" + + "\bbase_url\x18\x01 \x01(\tR\abaseUrl\"/\n" + + "\x11VllmAdapterConfig\x12\x1a\n" + + "\bendpoint\x18\x01 \x01(\tR\bendpoint\"\\\n" + "\x11NodeRuntimeConfig\x12 \n" + "\vconcurrency\x18\x01 \x01(\x05R\vconcurrency\x12%\n" + "\x0eworkspace_root\x18\x02 \x01(\tR\rworkspaceRoot*\x81\x01\n" + @@ -1340,7 +1742,7 @@ func file_proto_iop_runtime_proto_rawDescGZIP() []byte { } var file_proto_iop_runtime_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_proto_iop_runtime_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_proto_iop_runtime_proto_msgTypes = make([]protoimpl.MessageInfo, 24) var file_proto_iop_runtime_proto_goTypes = []any{ (RunSessionMode)(0), // 0: iop.RunSessionMode (CancelAction)(0), // 1: iop.CancelAction @@ -1358,35 +1760,47 @@ var file_proto_iop_runtime_proto_goTypes = []any{ (*RegisterResponse)(nil), // 13: iop.RegisterResponse (*NodeConfigPayload)(nil), // 14: iop.NodeConfigPayload (*AdapterConfig)(nil), // 15: iop.AdapterConfig - (*NodeRuntimeConfig)(nil), // 16: iop.NodeRuntimeConfig - nil, // 17: iop.RunRequest.MetadataEntry - nil, // 18: iop.RunEvent.MetadataEntry - nil, // 19: iop.NodeCommandRequest.MetadataEntry - nil, // 20: iop.AgentUsageStatus.MetadataEntry - (*structpb.Struct)(nil), // 21: google.protobuf.Struct + (*CLIAdapterConfig)(nil), // 16: iop.CLIAdapterConfig + (*CLIProfileConfig)(nil), // 17: iop.CLIProfileConfig + (*CLICompletionMarker)(nil), // 18: iop.CLICompletionMarker + (*OllamaAdapterConfig)(nil), // 19: iop.OllamaAdapterConfig + (*VllmAdapterConfig)(nil), // 20: iop.VllmAdapterConfig + (*NodeRuntimeConfig)(nil), // 21: iop.NodeRuntimeConfig + nil, // 22: iop.RunRequest.MetadataEntry + nil, // 23: iop.RunEvent.MetadataEntry + nil, // 24: iop.NodeCommandRequest.MetadataEntry + nil, // 25: iop.AgentUsageStatus.MetadataEntry + nil, // 26: iop.CLIAdapterConfig.ProfilesEntry + (*structpb.Struct)(nil), // 27: google.protobuf.Struct } var file_proto_iop_runtime_proto_depIdxs = []int32{ - 21, // 0: iop.RunRequest.policy:type_name -> google.protobuf.Struct - 21, // 1: iop.RunRequest.input:type_name -> google.protobuf.Struct - 17, // 2: iop.RunRequest.metadata:type_name -> iop.RunRequest.MetadataEntry + 27, // 0: iop.RunRequest.policy:type_name -> google.protobuf.Struct + 27, // 1: iop.RunRequest.input:type_name -> google.protobuf.Struct + 22, // 2: iop.RunRequest.metadata:type_name -> iop.RunRequest.MetadataEntry 0, // 3: iop.RunRequest.session_mode:type_name -> iop.RunSessionMode 5, // 4: iop.RunEvent.usage:type_name -> iop.Usage - 18, // 5: iop.RunEvent.metadata:type_name -> iop.RunEvent.MetadataEntry + 23, // 5: iop.RunEvent.metadata:type_name -> iop.RunEvent.MetadataEntry 1, // 6: iop.CancelRequest.action:type_name -> iop.CancelAction 2, // 7: iop.NodeCommandRequest.type:type_name -> iop.NodeCommandType - 19, // 8: iop.NodeCommandRequest.metadata:type_name -> iop.NodeCommandRequest.MetadataEntry + 24, // 8: iop.NodeCommandRequest.metadata:type_name -> iop.NodeCommandRequest.MetadataEntry 2, // 9: iop.NodeCommandResponse.type:type_name -> iop.NodeCommandType 10, // 10: iop.NodeCommandResponse.usage_status:type_name -> iop.AgentUsageStatus - 20, // 11: iop.AgentUsageStatus.metadata:type_name -> iop.AgentUsageStatus.MetadataEntry + 25, // 11: iop.AgentUsageStatus.metadata:type_name -> iop.AgentUsageStatus.MetadataEntry 14, // 12: iop.RegisterResponse.config:type_name -> iop.NodeConfigPayload 15, // 13: iop.NodeConfigPayload.adapters:type_name -> iop.AdapterConfig - 16, // 14: iop.NodeConfigPayload.runtime:type_name -> iop.NodeRuntimeConfig - 21, // 15: iop.AdapterConfig.settings:type_name -> google.protobuf.Struct - 16, // [16:16] is the sub-list for method output_type - 16, // [16:16] is the sub-list for method input_type - 16, // [16:16] is the sub-list for extension type_name - 16, // [16:16] is the sub-list for extension extendee - 0, // [0:16] is the sub-list for field type_name + 21, // 14: iop.NodeConfigPayload.runtime:type_name -> iop.NodeRuntimeConfig + 27, // 15: iop.AdapterConfig.settings:type_name -> google.protobuf.Struct + 16, // 16: iop.AdapterConfig.cli:type_name -> iop.CLIAdapterConfig + 19, // 17: iop.AdapterConfig.ollama:type_name -> iop.OllamaAdapterConfig + 20, // 18: iop.AdapterConfig.vllm:type_name -> iop.VllmAdapterConfig + 26, // 19: iop.CLIAdapterConfig.profiles:type_name -> iop.CLIAdapterConfig.ProfilesEntry + 18, // 20: iop.CLIProfileConfig.completion_marker:type_name -> iop.CLICompletionMarker + 17, // 21: iop.CLIAdapterConfig.ProfilesEntry.value:type_name -> iop.CLIProfileConfig + 22, // [22:22] is the sub-list for method output_type + 22, // [22:22] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name } func init() { file_proto_iop_runtime_proto_init() } @@ -1394,13 +1808,18 @@ func file_proto_iop_runtime_proto_init() { if File_proto_iop_runtime_proto != nil { return } + file_proto_iop_runtime_proto_msgTypes[12].OneofWrappers = []any{ + (*AdapterConfig_Cli)(nil), + (*AdapterConfig_Ollama)(nil), + (*AdapterConfig_Vllm)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_iop_runtime_proto_rawDesc), len(file_proto_iop_runtime_proto_rawDesc)), NumEnums: 3, - NumMessages: 18, + NumMessages: 24, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/iop/runtime.proto b/proto/iop/runtime.proto index 25476fe..934cc2c 100644 --- a/proto/iop/runtime.proto +++ b/proto/iop/runtime.proto @@ -132,7 +132,43 @@ message NodeConfigPayload { message AdapterConfig { string type = 1; // "mock" | "ollama" | "vllm" | "cli" bool enabled = 2; - google.protobuf.Struct settings = 3; + google.protobuf.Struct settings = 3; // legacy/compat path; new adapters use oneof + oneof config { + CLIAdapterConfig cli = 4; + OllamaAdapterConfig ollama = 5; + VllmAdapterConfig vllm = 6; + } +} + +message CLIAdapterConfig { + map profiles = 1; +} + +message CLIProfileConfig { + string command = 1; + repeated string args = 2; + repeated string env = 3; + bool persistent = 4; + bool terminal = 5; + int32 response_idle_timeout_ms = 6; + int32 startup_idle_timeout_ms = 7; + string output_format = 8; + CLICompletionMarker completion_marker = 9; + string mode = 10; + repeated string resume_args = 11; +} + +message CLICompletionMarker { + string line = 1; + string regex = 2; +} + +message OllamaAdapterConfig { + string base_url = 1; +} + +message VllmAdapterConfig { + string endpoint = 1; } // NodeRuntimeConfig is the runtime tuning pushed to the node.