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
This commit is contained in:
parent
86ceabadc3
commit
f873e27a15
9 changed files with 950 additions and 569 deletions
|
|
@ -1,74 +0,0 @@
|
|||
<!-- task=07_cli_profile_proto_message plan=0 tag=API -->
|
||||
|
||||
# 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)
|
||||
```
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
<!-- task=07_cli_profile_proto_message plan=0 tag=API -->
|
||||
|
||||
# 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<string, CLIProfileConfig> 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가 동작.
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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<string, CLIProfileConfig> 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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue