feat: edge runtime bridge contract & validation, automation phase milestone
This commit is contained in:
parent
e66e9fd236
commit
7112edfeef
16 changed files with 545 additions and 32 deletions
|
|
@ -36,6 +36,7 @@ Control Plane은 Edge 연결 view와 제어 요청/결과를 관리하고, Edge
|
|||
- `EdgeHelloRequest.edge_id`: Control Plane connection registry의 Edge identity다. 비어 있으면 hello는 거부된다.
|
||||
- `EdgeStatusResponse.nodes`: Edge가 소유한 node snapshot view다.
|
||||
- `EdgeNodeSnapshot.config`: Node에 내려간 config payload의 관찰용 요약이다.
|
||||
- `EdgeNodeSnapshot.provider_snapshots`: runtime `ProviderSnapshot` wire name을 재사용한 Node resource/provider snapshot이다. `category`가 CLI/API/local inference resource kind를 구분하며, Node address, token, transport internals는 싣지 않는다.
|
||||
- `EdgeCommandRequest.operation`: Edge-owned operation 이름이다. Node 직접 scheduling 명령으로 사용하지 않는다.
|
||||
- `EdgeCommandRequest.target_selector`: Edge 내부 operation이 해석할 대상 selector다. Node address나 token을 외부화하지 않는다.
|
||||
- `metadata`: 필요한 Edge identity 또는 운영 보조 정보만 담는다. secret과 private endpoint 원문은 tracked 계약에 쓰지 않는다.
|
||||
|
|
|
|||
|
|
@ -31,14 +31,16 @@ tracked config에는 public 예시와 기본 구조만 두고, 실제 endpoint/c
|
|||
|
||||
- `openai.model_routes[]`는 외부 OpenAI-compatible `model` id를 내부 `adapter + target` route로 매핑하는 compatibility catalog다.
|
||||
- `models[]`는 provider pool 방향의 canonical routing key이며 `nodes[].providers[].id`를 참조한다.
|
||||
- `nodes[].providers[]`는 Node 아래 resource/provider catalog다. `category`는 `api`, `cli`, `local_inference` resource kind를 나타낸다.
|
||||
- `nodes[].providers[].id`는 전체 Edge config 안에서 중복되면 안 된다.
|
||||
- `nodes[].providers[].adapter`는 같은 Node 안의 enabled adapter instance key를 참조해야 한다. Exact instance key를 우선하고, legacy type-name route는 같은 type의 enabled instance가 정확히 하나일 때만 허용한다. `category: cli` resource는 enabled CLI adapter가 필요하다.
|
||||
- legacy single-instance adapter 설정은 load 시 named instance slice로 normalize된다.
|
||||
- `NodeConfigPayload`는 Edge가 Node에 내려주는 실행 adapter/runtime payload다.
|
||||
- refresh 결과는 `applied`, `restart_required`, `rejected`를 구분하고, changed node/provider/model/report slice는 안정적으로 non-nil이어야 한다.
|
||||
|
||||
## refresh 분류 기준
|
||||
|
||||
- live apply 가능: provider capacity, provider max queue, provider queue timeout, `models[]` display/provider mapping, node runtime concurrency.
|
||||
- live apply 가능: provider capacity, provider max queue, provider queue timeout, `models[]` display/provider mapping, legacy node runtime concurrency metadata.
|
||||
- restart required: Edge identity/listen/bootstrap/logging/metrics/console/control-plane/openai/a2a listener config, node 추가/삭제, node token/alias/agent kind, adapter 설정, provider type/category/adapter/models/health/lifecycle capability.
|
||||
- rejected: candidate config load/validate 실패, invalid refresh mode, apply failure.
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ Edge는 Node 연결을 수락하고, Node는 연결 직후 등록 요청을 보
|
|||
- `NodeCommandRequest.type`: 실행이 아닌 조회/제어성 명령이다. adapter execution 요청과 섞지 않는다.
|
||||
- `NodeConfigPayload.adapters`: Edge가 Node에 내려주는 adapter instance 설정이다.
|
||||
- `AdapterConfig.name`: node 내부 stable adapter instance identity다. 비어 있으면 legacy single-instance type 이름과 동등하다.
|
||||
- `NodeRuntimeConfig.concurrency`: node-wide 실행 동시성 제한이다. Node store 위치나 CLI 실행 작업 디렉터리는 이 runtime payload에 싣지 않는다.
|
||||
- `NodeRuntimeConfig.concurrency`: legacy compatibility runtime metadata다. 실행 admission은 이 값을 node-wide global gate로 사용하지 않고 provider/resource capacity를 기준으로 한다. Node store 위치나 CLI 실행 작업 디렉터리는 이 runtime payload에 싣지 않는다.
|
||||
- `ProviderSnapshot`: legacy wire name을 유지하지만 Node 아래 resource/provider 상태 snapshot으로 해석한다. `category`가 `api`, `cli`, `local_inference` resource kind를 나타내며, provider-pool dispatch 대상은 Edge config `models[].providers`가 참조한 resource뿐이다.
|
||||
|
||||
## 금지 사항
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ NomadCode가 IOP를 실행 백엔드로 사용할 수 있도록 하는 Responses
|
|||
- 경로: `agent-roadmap/archive/phase/automation-runtime-bridge/milestones/openai-workspace-agent-execution-contract.md`
|
||||
- 요약: NomadCode가 IOP CLI를 직접 실행하지 않고 IOP Edge OpenAI-compatible HTTP 호출의 `metadata.workspace`와 task/source metadata만으로 내부 workspace-bound agent target이 해당 checkout에서 산출물을 만들 수 있게 하는 최우선 contract/serving hardening 작업이다.
|
||||
|
||||
- [계획] Pi CLI Provider Integration
|
||||
- 경로: `agent-roadmap/phase/automation-runtime-bridge/milestones/pi-cli-provider-integration.md`
|
||||
- 요약: Node CLI adapter의 실행 profile 후보에 Pi를 추가하고, Pi JSON stream 출력 파서, config 예시, OpenAI-compatible route smoke를 통해 `adapter=cli,target=pi`를 안정적으로 사용할 수 있게 한다.
|
||||
|
||||
- [계획] CLI Agent Group Grade Routing
|
||||
- 경로: `agent-roadmap/phase/automation-runtime-bridge/milestones/cli-agent-group-grade-routing.md`
|
||||
- 요약: `PLAN-local-G08.md`, `CODE_REVIEW-cloud-G07.md` 같은 예약어/lane/grade 파일명을 기준으로 CLI provider agent를 목적별 agent group에 라우팅하고, 수동/자동 grade range assignment와 OpenAI-compatible `metadata.agent_group.task_file` 계약을 정리한다.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
# Milestone: Pi CLI Provider Integration
|
||||
|
||||
## 위치
|
||||
|
||||
- Roadmap: `agent-roadmap/ROADMAP.md`
|
||||
- Phase: `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
|
||||
|
||||
## 목표
|
||||
|
||||
Node CLI adapter의 provider/profile 후보에 Pi를 추가해 `adapter=cli,target=pi` 실행을 OpenAI-compatible route와 내부 run dispatch에서 안정적으로 사용할 수 있게 한다.
|
||||
Pi의 JSON streaming 출력은 IOP runtime event로 변환하고, 기본 config 예시는 tools-enabled Pi 실행을 기준으로 둔다.
|
||||
|
||||
## 상태
|
||||
|
||||
[계획]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
- 없음
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 해제
|
||||
- SDD: 불필요
|
||||
- SDD 문서: 없음
|
||||
- SDD 사유: 기존 `CLIProfileConf`, CLI adapter 실행 모델, OpenAI-compatible `adapter + target` route를 재사용하는 provider profile 추가이며 새 wire/API/schema 계약을 만들지 않는다.
|
||||
- 잠금 해제 조건: 없음
|
||||
- 결정 필요: 없음
|
||||
|
||||
## 범위
|
||||
|
||||
- Node CLI adapter가 Pi를 실행 가능한 target profile로 등록하고, 기본 실행은 headless JSON stream 모드를 사용한다.
|
||||
- Pi profile은 기본적으로 tools-enabled 실행을 유지한다. 스트리밍 회피용 `--no-tools`를 기본 예시나 smoke 기준으로 사용하지 않는다.
|
||||
- Pi JSON stream의 `message_update`/text delta, completion, error event를 IOP runtime event로 변환한다.
|
||||
- OpenAI-compatible route 또는 model alias가 `adapter=cli,target=pi`로 연결될 때 workspace-bound prompt가 Pi 실행으로 전달되는지 확인한다.
|
||||
- Pi 실행에 필요한 로컬 설정 파일과 model/provider 설정은 Pi 자체 설정을 source of truth로 두고, IOP config는 Pi command/profile/route만 소유한다.
|
||||
|
||||
## 기능
|
||||
|
||||
### Epic: [profile] Pi CLI profile
|
||||
|
||||
Pi를 Node CLI adapter의 일반 실행 target으로 등록하고, dev/local config에서 바로 켤 수 있는 예시를 제공한다.
|
||||
|
||||
- [ ] [pi-profile] `nodes[].providers[].cli.profiles.pi` 또는 동등한 CLI profile 예시가 `command=pi`, JSON streaming mode, workspace 전달, timeout 기본값을 포함한다. 검증: config roundtrip에서 `target=pi`가 CLI adapter capability로 노출된다.
|
||||
- [ ] [pi-route] OpenAI-compatible `model_routes` 또는 provider catalog 예시가 `model=pi`를 `adapter=cli,target=pi`로 라우팅하고, CLI target 특성상 `metadata.workspace`가 필요한 경로임을 문서화한다.
|
||||
- [ ] [tools-enabled] 기본 Pi profile과 smoke 예시는 `--no-tools`를 사용하지 않는다. 검증: tools 포함 요청에서도 첫 text delta가 completion 이전에 관측된다.
|
||||
|
||||
### Epic: [stream] Pi JSON stream 변환
|
||||
|
||||
Pi headless JSON 출력 이벤트를 IOP runtime event로 변환해 기존 CLI streaming 경로와 같은 의미로 제공한다.
|
||||
|
||||
- [ ] [pi-emitter] Pi JSON stream emitter가 text delta, final message, usage/error event를 파싱해 `runtime.RuntimeEvent`로 변환한다. 검증: fake Pi JSON lines fixture에서 delta가 순서대로 emit되고 completion event가 한 번만 발생한다.
|
||||
- [ ] [pi-error] Pi 프로세스 종료, malformed JSON, provider error, context window exceeded 같은 실패가 route error와 log에 구분되어 남는다. 검증: 각 실패 fixture가 사용자에게 원인 메시지를 보존한 error event로 변환된다.
|
||||
- [ ] [pi-workspace] workspace-bound 실행에서 Pi 프로세스 cwd와 session/no-session 정책이 profile 설정에 맞게 적용된다. 검증: 상대 workspace 요청이 해당 checkout에서 실행되고, workspace가 필요한 route에서 누락 시 입력 오류를 반환한다.
|
||||
|
||||
### Epic: [docs-tests] 문서와 검증
|
||||
|
||||
운영자가 Pi CLI provider를 켜고 문제를 재현할 수 있도록 예시와 smoke를 남긴다.
|
||||
|
||||
- [ ] [config-docs] Edge/Node 운영 문서와 config sample에 Pi profile, route 예시, 필수 Pi 설정 파일, context window/client 설정 주의사항을 추가한다.
|
||||
- [ ] [unit-tests] CLI emitter, profile capability, route mapping, workspace-required error에 대한 단위 테스트가 추가된다.
|
||||
- [ ] [dev-smoke] dev-runtime에서 `adapter=cli,target=pi`로 짧은 OpenAI-compatible streaming smoke를 실행하고, tools-enabled 요청이 completion 전 delta를 반환하는 근거를 남긴다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 기능 Task가 아직 충족되지 않았다.
|
||||
- 검토 항목:
|
||||
- [ ] Pi profile이 CLI adapter capability와 config sample에 노출된다
|
||||
- [ ] Pi JSON stream emitter가 delta/error/completion을 안정적으로 변환한다
|
||||
- [ ] tools-enabled streaming smoke 근거가 남아 있다
|
||||
- 리뷰 코멘트: 없음
|
||||
|
||||
## 범위 제외
|
||||
|
||||
- Pi 자체 provider/model 설정 파일의 소유권 이전 또는 자동 생성. Pi 설정은 Pi가 소유하고 IOP는 실행 profile과 route만 소유한다.
|
||||
- Pi TUI 화면 렌더링을 IOP terminal bridge로 중계하는 기능.
|
||||
- Pi 내부 tool 목록, MCP 정책, auth/provider 설정을 IOP schema로 재정의하는 기능.
|
||||
- CLI Agent Group Grade Routing의 agent group assignment 정책. Pi는 이 Milestone에서 routing 가능한 CLI target으로 준비하고, grade routing 편입은 후속 Milestone에서 다룬다.
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `apps/node/internal/adapters/cli`, `packages/go/config`, `configs/edge.yaml`, `configs/edge-compose.yaml.tmpl`, `apps/edge/README.md`, `agent-contract/outer/openai-compatible-api.md`
|
||||
- 표준선(선택): 내부 실행 개념은 기존처럼 `adapter + target`을 유지한다. Pi는 새 top-level adapter가 아니라 `cli` adapter의 target/profile로 추가한다.
|
||||
- 선행 작업: CLI Automation Runtime 안정화, OpenAI Workspace Agent Execution Contract
|
||||
- 후속 작업: CLI Agent Group Grade Routing에서 Pi target을 agent group 후보로 포함한다.
|
||||
- 확인 필요: 없음
|
||||
|
|
@ -32,18 +32,18 @@ task=m-node-resource-model-unification/01_contract_schema, plan=0, tag=API
|
|||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [API-1] Runtime Concurrency Contract | [ ] |
|
||||
| [API-2] Resource Catalog Validation | [ ] |
|
||||
| [API-3] Resource Snapshot Compatibility | [ ] |
|
||||
| [API-1] Runtime Concurrency Contract | [x] |
|
||||
| [API-2] Resource Catalog Validation | [x] |
|
||||
| [API-3] Resource Snapshot Compatibility | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `NodeRuntimeConfig.concurrency`를 node-wide admission source가 아닌 legacy/compat runtime payload로 계약 문서와 comments에서 정리한다.
|
||||
- [ ] `nodes[].providers[]`를 현재 wire의 resource catalog로 해석한다는 compat 기준을 문서화하고, `category=cli|api|local_inference`가 CLI/provider/resource 구분자임을 명시한다.
|
||||
- [ ] `nodes[].providers[].adapter`가 같은 node 안의 enabled adapter instance로 해석되어야 한다는 config validation을 추가한다.
|
||||
- [ ] provider adapter reference, duplicate/resource category, legacy config compatibility regression test를 추가한다.
|
||||
- [ ] proto 변경이 발생하면 `make proto`를 실행하고 generated diff가 원본 proto와 일치하는지 확인한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
- [x] `NodeRuntimeConfig.concurrency`를 node-wide admission source가 아닌 legacy/compat runtime payload로 계약 문서와 comments에서 정리한다.
|
||||
- [x] `nodes[].providers[]`를 현재 wire의 resource catalog로 해석한다는 compat 기준을 문서화하고, `category=cli|api|local_inference`가 CLI/provider/resource 구분자임을 명시한다.
|
||||
- [x] `nodes[].providers[].adapter`가 같은 node 안의 enabled adapter instance로 해석되어야 한다는 config validation을 추가한다.
|
||||
- [x] provider adapter reference, duplicate/resource category, legacy config compatibility regression test를 추가한다.
|
||||
- [x] proto 변경이 발생하면 `make proto`를 실행하고 generated diff가 원본 proto와 일치하는지 확인한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
|
|
@ -59,11 +59,15 @@ task=m-node-resource-model-unification/01_contract_schema, plan=0, tag=API
|
|||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
- Validation 구현 위치는 계획의 선택지 중 `apps/edge/internal/edgevalidate`로 고정했다. `config.LoadEdge`는 legacy YAML 파싱/구조 검증을 유지하고, `config check`와 refresh candidate loader가 공유하는 runtime validation 단계에서 provider adapter reference를 검증한다.
|
||||
- 새 validator 때문에 refresh 관련 test YAML fixture의 `nodes[].providers[].adapter`를 명시했다. 이는 새 계약에 맞춘 fixture 정리이며 runtime source behavior 확장은 아니다.
|
||||
- Proto field 추가/삭제 없이 comment-only 계약 변경으로 처리했고, `make proto` 결과 생성물 comment diff만 발생했다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
- Provider/resource adapter reference는 exact enabled instance key를 먼저 인정하고, legacy type-name route는 같은 type의 enabled instance가 정확히 하나일 때만 인정한다.
|
||||
- `category: cli` resource는 enabled CLI adapter가 있어야 한다. CLI 외 category는 `type`과 adapter type을 강제 매칭하지 않았다. 예: `type: vllm` provider가 `openai_compat` adapter instance를 통해 실행되는 기존 provider 패턴을 보존하기 위해서다.
|
||||
- `runtime.concurrency`와 `NodeRuntimeConfig.concurrency`는 01번 범위에서 legacy compatibility metadata로 문서화만 했다. Node admission 제거는 후속 `02+01_node_admission` task 범위로 남겼다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
|
|
@ -91,25 +95,133 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후
|
|||
### API-1 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./apps/edge/internal/configrefresh
|
||||
(output)
|
||||
ok iop/apps/edge/internal/configrefresh 0.017s
|
||||
```
|
||||
|
||||
### API-2 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./packages/go/config ./apps/edge/internal/edgevalidate
|
||||
(output)
|
||||
ok iop/packages/go/config 0.040s
|
||||
ok iop/apps/edge/internal/edgevalidate 0.004s
|
||||
```
|
||||
|
||||
### API-3 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./proto/gen/...
|
||||
(output)
|
||||
? iop/proto/gen/iop [no test files]
|
||||
```
|
||||
|
||||
```text
|
||||
$ make proto
|
||||
protoc \
|
||||
--go_out=. \
|
||||
--go_opt=module=iop \
|
||||
--proto_path=. \
|
||||
proto/iop/runtime.proto \
|
||||
proto/iop/node.proto \
|
||||
proto/iop/control.proto \
|
||||
proto/iop/job.proto
|
||||
```
|
||||
|
||||
```text
|
||||
$ git diff -- proto/gen/iop
|
||||
diff --git a/proto/gen/iop/control.pb.go b/proto/gen/iop/control.pb.go
|
||||
index 05e4b78..362250f 100644
|
||||
--- a/proto/gen/iop/control.pb.go
|
||||
+++ b/proto/gen/iop/control.pb.go
|
||||
@@ -490,13 +490,15 @@ func (x *EdgeStatusRequest) GetRequestId() string {
|
||||
// only mirrors what the Edge service exposes and must not carry Node address,
|
||||
// token, or scheduling fields.
|
||||
type EdgeNodeSnapshot struct {
|
||||
- state protoimpl.MessageState `protogen:"open.v1"`
|
||||
- NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
||||
- Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"`
|
||||
- Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"`
|
||||
- Connected bool `protobuf:"varint,4,opt,name=connected,proto3" json:"connected,omitempty"`
|
||||
- Config *NodeConfigPayload `protobuf:"bytes,5,opt,name=config,proto3" json:"config,omitempty"`
|
||||
- ProviderSnapshots []*ProviderSnapshot `protobuf:"bytes,6,rep,name=provider_snapshots,json=providerSnapshots,proto3" json:"provider_snapshots,omitempty"`
|
||||
+ state protoimpl.MessageState `protogen:"open.v1"`
|
||||
+ NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
||||
+ Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"`
|
||||
+ Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"`
|
||||
+ Connected bool `protobuf:"varint,4,opt,name=connected,proto3" json:"connected,omitempty"`
|
||||
+ Config *NodeConfigPayload `protobuf:"bytes,5,opt,name=config,proto3" json:"config,omitempty"`
|
||||
+ // provider_snapshots uses the runtime ProviderSnapshot wire name for node
|
||||
+ // resources/providers. category distinguishes CLI/API/local inference.
|
||||
+ ProviderSnapshots []*ProviderSnapshot `protobuf:"bytes,6,rep,name=provider_snapshots,json=providerSnapshots,proto3" json:"provider_snapshots,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
diff --git a/proto/gen/iop/runtime.pb.go b/proto/gen/iop/runtime.pb.go
|
||||
index 803a783..bfcfd3d 100644
|
||||
--- a/proto/gen/iop/runtime.pb.go
|
||||
+++ b/proto/gen/iop/runtime.pb.go
|
||||
@@ -970,6 +970,10 @@ func (x *NodeCommandResponse) GetProviderSnapshots() []*ProviderSnapshot {
|
||||
return nil
|
||||
}
|
||||
|
||||
+// ProviderSnapshot is the legacy wire name for a node resource/provider status
|
||||
+// snapshot. category identifies whether the resource is API, CLI, or local
|
||||
+// inference; provider-pool dispatch is limited to catalog resources referenced
|
||||
+// by models[].providers.
|
||||
type ProviderSnapshot struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Adapter string `protobuf:"bytes,1,opt,name=adapter,proto3" json:"adapter,omitempty"`
|
||||
@@ -2106,10 +2110,12 @@ func (x *OpenAICompatAdapterConfig) GetRequestTimeoutMs() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
-// NodeRuntimeConfig is the runtime tuning pushed to the node.
|
||||
+// NodeRuntimeConfig carries legacy node runtime metadata. Execution admission
|
||||
+// must not use this as a node-wide global gate; provider/resource capacity owns
|
||||
+// concurrency.
|
||||
type NodeRuntimeConfig struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
- Concurrency int32 `protobuf:"varint,1,opt,name=concurrency,proto3" json:"concurrency,omitempty"`
|
||||
+ Concurrency int32 `protobuf:"varint,1,opt,name=concurrency,proto3" json:"concurrency,omitempty"` // legacy compatibility, 0/unset means no node-wide limit
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```text
|
||||
$ go test -count=1 ./packages/go/... ./proto/gen/... ./apps/edge/internal/configrefresh ./apps/edge/internal/node
|
||||
(output)
|
||||
ok iop/packages/go/audit 0.002s
|
||||
? iop/packages/go/auth [no test files]
|
||||
ok iop/packages/go/config 0.040s
|
||||
? iop/packages/go/events [no test files]
|
||||
ok iop/packages/go/hostsetup 0.007s
|
||||
? iop/packages/go/jobs [no test files]
|
||||
? iop/packages/go/metadata [no test files]
|
||||
ok iop/packages/go/observability 0.010s
|
||||
? iop/packages/go/policy [no test files]
|
||||
? iop/packages/go/version [no test files]
|
||||
? iop/proto/gen/iop [no test files]
|
||||
ok iop/apps/edge/internal/configrefresh 0.014s
|
||||
ok iop/apps/edge/internal/node 0.004s
|
||||
```
|
||||
|
||||
```text
|
||||
$ go test -count=1 ./apps/edge/...
|
||||
ok iop/apps/edge/cmd/edge 0.041s
|
||||
ok iop/apps/edge/internal/bootstrap 0.280s
|
||||
ok iop/apps/edge/internal/configrefresh 0.025s
|
||||
ok iop/apps/edge/internal/controlplane 4.450s
|
||||
ok iop/apps/edge/internal/edgecmd 0.029s
|
||||
ok iop/apps/edge/internal/edgevalidate 0.015s
|
||||
ok iop/apps/edge/internal/events 0.013s
|
||||
ok iop/apps/edge/internal/input 0.013s
|
||||
ok iop/apps/edge/internal/input/a2a 0.008s
|
||||
ok iop/apps/edge/internal/node 0.005s
|
||||
ok iop/apps/edge/internal/openai 1.514s
|
||||
ok iop/apps/edge/internal/opsconsole 0.006s
|
||||
ok iop/apps/edge/internal/service 0.439s
|
||||
ok iop/apps/edge/internal/transport 2.038s
|
||||
```
|
||||
|
||||
```text
|
||||
$ git diff --check
|
||||
```
|
||||
|
||||
> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
max_queue: 4
|
||||
|
|
@ -254,6 +255,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 8
|
||||
max_queue: 4
|
||||
|
|
@ -880,6 +882,7 @@ nodes:
|
|||
- id: "prov-r"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3"]
|
||||
capacity: %d
|
||||
max_queue: 4
|
||||
|
|
@ -1092,6 +1095,7 @@ nodes:
|
|||
- id: "prov-skip"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3"]
|
||||
capacity: %d
|
||||
max_queue: 4
|
||||
|
|
@ -1247,6 +1251,7 @@ nodes:
|
|||
- id: "prov-nc"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3"]
|
||||
capacity: 2
|
||||
max_queue: 4
|
||||
|
|
|
|||
|
|
@ -217,7 +217,8 @@ func Classify(current, candidate *config.EdgeConfig) Result {
|
|||
appendIfChanged(&changes, fmt.Sprintf("nodes[%q].token", key), StatusRestartRequired, cur.Token, next.Token)
|
||||
appendIfChanged(&changes, fmt.Sprintf("nodes[%q].agent_kind", key), StatusRestartRequired, cur.AgentKind, next.AgentKind)
|
||||
appendDeepIfChanged(&changes, fmt.Sprintf("nodes[%q].adapters", key), StatusRestartRequired, cur.Adapters, next.Adapters)
|
||||
// Node-wide concurrency is live-applyable.
|
||||
// Legacy runtime concurrency metadata is live-applyable for compat.
|
||||
// Runtime admission is owned by provider/resource capacity.
|
||||
appendIfChanged(&changes, fmt.Sprintf("nodes[%q].runtime.concurrency", key), StatusApplied, cur.Runtime.Concurrency, next.Runtime.Concurrency)
|
||||
}
|
||||
for key := range candidateNodes {
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
max_queue: 4
|
||||
|
|
@ -109,6 +110,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 8
|
||||
max_queue: 4
|
||||
|
|
@ -166,6 +168,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
max_queue: 4
|
||||
|
|
@ -239,11 +242,13 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
- id: "prov-b"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
models:
|
||||
|
|
@ -266,11 +271,13 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
- id: "prov-b"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
models:
|
||||
|
|
@ -323,6 +330,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "vllm"
|
||||
category: "api"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
max_queue: 4
|
||||
|
|
@ -420,6 +428,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
max_queue: 4
|
||||
|
|
@ -444,6 +453,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 8
|
||||
max_queue: 4
|
||||
|
|
@ -540,6 +550,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
max_queue: 4
|
||||
|
|
@ -564,6 +575,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 8
|
||||
max_queue: 4
|
||||
|
|
@ -645,6 +657,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 2
|
||||
`
|
||||
|
|
@ -662,6 +675,7 @@ nodes:
|
|||
- id: "prov-a"
|
||||
type: "ollama"
|
||||
category: "local_inference"
|
||||
adapter: "cli"
|
||||
models: ["llama3.1"]
|
||||
capacity: 4
|
||||
`
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package edgevalidate
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
edgenode "iop/apps/edge/internal/node"
|
||||
"iop/packages/go/config"
|
||||
|
|
@ -60,10 +61,105 @@ func ValidateEdgeConfig(cfg *config.EdgeConfig) error {
|
|||
if n.Runtime.Concurrency < 0 {
|
||||
return fmt.Errorf("node %q: runtime concurrency must be non-negative", name)
|
||||
}
|
||||
if err := validateProviderAdapterReferences(name, n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateProviderAdapterReferences(nodeName string, n config.NodeDefinition) error {
|
||||
idx := buildAdapterIndex(n.Adapters)
|
||||
for i, p := range n.Providers {
|
||||
adapter := strings.TrimSpace(p.Adapter)
|
||||
if adapter == "" {
|
||||
return fmt.Errorf("node %q: providers[%d] %q: adapter must not be empty", nodeName, i, p.ID)
|
||||
}
|
||||
if p.Category == config.CategoryCLI && !n.Adapters.CLI.Enabled {
|
||||
return fmt.Errorf("node %q: providers[%d] %q: category cli requires enabled cli adapter", nodeName, i, p.ID)
|
||||
}
|
||||
if idx.exact[adapter] {
|
||||
continue
|
||||
}
|
||||
if count, knownType := idx.typeCount[adapter]; knownType {
|
||||
switch count {
|
||||
case 0:
|
||||
return fmt.Errorf("node %q: providers[%d] %q: adapter %q does not resolve to an enabled adapter instance", nodeName, i, p.ID, adapter)
|
||||
case 1:
|
||||
continue
|
||||
default:
|
||||
return fmt.Errorf("node %q: providers[%d] %q: adapter %q is ambiguous; use an enabled adapter instance key", nodeName, i, p.ID, adapter)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("node %q: providers[%d] %q: adapter %q does not resolve to an enabled adapter instance", nodeName, i, p.ID, adapter)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type adapterIndex struct {
|
||||
exact map[string]bool
|
||||
typeKeys map[string]map[string]struct{}
|
||||
typeCount map[string]int
|
||||
}
|
||||
|
||||
func buildAdapterIndex(a config.AdaptersConf) adapterIndex {
|
||||
idx := adapterIndex{
|
||||
exact: make(map[string]bool),
|
||||
typeKeys: make(map[string]map[string]struct{}),
|
||||
typeCount: map[string]int{"ollama": 0, "vllm": 0, "openai_compat": 0, "cli": 0},
|
||||
}
|
||||
if a.Ollama.Enabled {
|
||||
idx.add("ollama", "ollama")
|
||||
}
|
||||
for _, inst := range a.OllamaInstances {
|
||||
if inst.Enabled {
|
||||
idx.add("ollama", defaultAdapterKey(inst.Name, "ollama"))
|
||||
}
|
||||
}
|
||||
if a.Vllm.Enabled {
|
||||
idx.add("vllm", "vllm")
|
||||
}
|
||||
for _, inst := range a.VllmInstances {
|
||||
if inst.Enabled {
|
||||
idx.add("vllm", defaultAdapterKey(inst.Name, "vllm"))
|
||||
}
|
||||
}
|
||||
if a.OpenAICompat.Enabled {
|
||||
idx.add("openai_compat", "openai_compat")
|
||||
}
|
||||
for _, inst := range a.OpenAICompatInstances {
|
||||
if inst.Enabled {
|
||||
idx.add("openai_compat", defaultAdapterKey(inst.Name, "openai_compat"))
|
||||
}
|
||||
}
|
||||
if a.CLI.Enabled {
|
||||
idx.add("cli", "cli")
|
||||
}
|
||||
for typeName, keys := range idx.typeKeys {
|
||||
idx.typeCount[typeName] = len(keys)
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
func (idx adapterIndex) add(typeName, key string) {
|
||||
key = strings.TrimSpace(key)
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
idx.exact[key] = true
|
||||
if idx.typeKeys[typeName] == nil {
|
||||
idx.typeKeys[typeName] = make(map[string]struct{})
|
||||
}
|
||||
idx.typeKeys[typeName][key] = struct{}{}
|
||||
}
|
||||
|
||||
func defaultAdapterKey(name, typeName string) string {
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return typeName
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func anyOllamaEnabled(a config.AdaptersConf) bool {
|
||||
if a.Ollama.Enabled {
|
||||
return true
|
||||
|
|
|
|||
167
apps/edge/internal/edgevalidate/validate_test.go
Normal file
167
apps/edge/internal/edgevalidate/validate_test.go
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
package edgevalidate
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"iop/packages/go/config"
|
||||
)
|
||||
|
||||
func TestValidateEdgeConfigProviderAdapterReferences(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
node config.NodeDefinition
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "valid exact openai compat instance",
|
||||
node: config.NodeDefinition{
|
||||
ID: "node-1",
|
||||
Token: "tok",
|
||||
Adapters: config.AdaptersConf{
|
||||
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
|
||||
{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
|
||||
},
|
||||
},
|
||||
Providers: []config.NodeProviderConf{
|
||||
{ID: "prov-vllm", Type: "vllm", Category: config.CategoryAPI, Adapter: "vllm-gpu"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid single type route",
|
||||
node: config.NodeDefinition{
|
||||
ID: "node-1",
|
||||
Token: "tok",
|
||||
Adapters: config.AdaptersConf{
|
||||
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
|
||||
{Name: "vllm-gpu", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
|
||||
},
|
||||
},
|
||||
Providers: []config.NodeProviderConf{
|
||||
{ID: "prov-vllm", Type: "vllm", Category: config.CategoryAPI, Adapter: "openai_compat"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing adapter rejected",
|
||||
node: config.NodeDefinition{
|
||||
ID: "node-1",
|
||||
Token: "tok",
|
||||
Adapters: config.AdaptersConf{
|
||||
CLI: config.CLIConf{Enabled: true},
|
||||
},
|
||||
Providers: []config.NodeProviderConf{
|
||||
{ID: "prov-vllm", Type: "vllm", Category: config.CategoryAPI, Adapter: "missing"},
|
||||
},
|
||||
},
|
||||
wantErr: "does not resolve",
|
||||
},
|
||||
{
|
||||
name: "disabled instance rejected",
|
||||
node: config.NodeDefinition{
|
||||
ID: "node-1",
|
||||
Token: "tok",
|
||||
Adapters: config.AdaptersConf{
|
||||
CLI: config.CLIConf{Enabled: true},
|
||||
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
|
||||
{Name: "vllm-gpu", Enabled: false, Endpoint: "http://127.0.0.1:8000/v1"},
|
||||
},
|
||||
},
|
||||
Providers: []config.NodeProviderConf{
|
||||
{ID: "prov-vllm", Type: "vllm", Category: config.CategoryAPI, Adapter: "vllm-gpu"},
|
||||
},
|
||||
},
|
||||
wantErr: "does not resolve",
|
||||
},
|
||||
{
|
||||
name: "ambiguous type route rejected",
|
||||
node: config.NodeDefinition{
|
||||
ID: "node-1",
|
||||
Token: "tok",
|
||||
Adapters: config.AdaptersConf{
|
||||
OpenAICompatInstances: []config.OpenAICompatInstanceConf{
|
||||
{Name: "vllm-a", Enabled: true, Endpoint: "http://127.0.0.1:8000/v1"},
|
||||
{Name: "vllm-b", Enabled: true, Endpoint: "http://127.0.0.1:8001/v1"},
|
||||
},
|
||||
},
|
||||
Providers: []config.NodeProviderConf{
|
||||
{ID: "prov-vllm", Type: "vllm", Category: config.CategoryAPI, Adapter: "openai_compat"},
|
||||
},
|
||||
},
|
||||
wantErr: "ambiguous",
|
||||
},
|
||||
{
|
||||
name: "cli resource requires enabled cli",
|
||||
node: config.NodeDefinition{
|
||||
ID: "node-1",
|
||||
Token: "tok",
|
||||
Adapters: config.AdaptersConf{
|
||||
OllamaInstances: []config.OllamaInstanceConf{
|
||||
{Name: "ollama-local", Enabled: true, BaseURL: "http://127.0.0.1:11434"},
|
||||
},
|
||||
},
|
||||
Providers: []config.NodeProviderConf{
|
||||
{ID: "cli-codex", Type: "cli", Category: config.CategoryCLI, Adapter: "cli"},
|
||||
},
|
||||
},
|
||||
wantErr: "category cli requires enabled cli adapter",
|
||||
},
|
||||
{
|
||||
name: "valid cli resource",
|
||||
node: config.NodeDefinition{
|
||||
ID: "node-1",
|
||||
Token: "tok",
|
||||
Adapters: config.AdaptersConf{
|
||||
CLI: config.CLIConf{Enabled: true},
|
||||
},
|
||||
Providers: []config.NodeProviderConf{
|
||||
{ID: "cli-codex", Type: "cli", Category: config.CategoryCLI, Adapter: "cli"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := ValidateEdgeConfig(&config.EdgeConfig{Nodes: []config.NodeDefinition{tc.node}})
|
||||
if tc.wantErr == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateEdgeConfig: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("expected error containing %q", tc.wantErr)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.wantErr) {
|
||||
t.Fatalf("expected error containing %q, got %v", tc.wantErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEdgeConfigProviderAdapterEmptyRejected(t *testing.T) {
|
||||
cfg := &config.EdgeConfig{
|
||||
Nodes: []config.NodeDefinition{
|
||||
{
|
||||
ID: "node-1",
|
||||
Token: "tok",
|
||||
Adapters: config.AdaptersConf{
|
||||
CLI: config.CLIConf{Enabled: true},
|
||||
},
|
||||
Providers: []config.NodeProviderConf{
|
||||
{ID: "prov-empty", Type: "cli", Category: config.CategoryCLI, Adapter: " "},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := ValidateEdgeConfig(cfg)
|
||||
if err == nil {
|
||||
t.Fatal("expected empty adapter error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "adapter must not be empty") {
|
||||
t.Fatalf("expected adapter empty error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -109,16 +109,20 @@ func validateCategory(c Category) error {
|
|||
}
|
||||
}
|
||||
|
||||
// NodeProviderConf defines a provider candidate that belongs to a node.
|
||||
// NodeProviderConf defines a resource/provider candidate that belongs to a
|
||||
// node. Edge validation resolves Adapter against enabled adapter instances on
|
||||
// the same node before this resource can be used for dispatch.
|
||||
type NodeProviderConf struct {
|
||||
// ID uniquely identifies this provider within the node and is referenced
|
||||
// by models[].providers keys.
|
||||
ID string `mapstructure:"id" yaml:"id"`
|
||||
// Type is the runtime type (e.g. "ollama", "vllm", "lemonade", "sglang", "openai_api", "cli").
|
||||
Type string `mapstructure:"type" yaml:"type"`
|
||||
// Category classifies the provider; MVP values: api, cli, local_inference.
|
||||
// Category classifies the resource; MVP values: api, cli, local_inference.
|
||||
Category Category `mapstructure:"category" yaml:"category"`
|
||||
// Adapter is the concrete adapter instance key used by the edge to dispatch.
|
||||
// Adapter is the concrete enabled adapter instance key used by the edge to
|
||||
// dispatch. A legacy type-name route is valid only when exactly one enabled
|
||||
// adapter instance of that type exists on the same node.
|
||||
Adapter string `mapstructure:"adapter" yaml:"adapter,omitempty"`
|
||||
// Models lists the served model names this provider can actually serve.
|
||||
Models []string `mapstructure:"models" yaml:"models,omitempty"`
|
||||
|
|
@ -328,6 +332,8 @@ type TLSConf struct {
|
|||
}
|
||||
|
||||
type RuntimeConf struct {
|
||||
// Concurrency is legacy compatibility metadata. Runtime admission must use
|
||||
// provider/resource capacity rather than treating this as a node-wide gate.
|
||||
Concurrency int `mapstructure:"concurrency" yaml:"concurrency"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -490,13 +490,15 @@ func (x *EdgeStatusRequest) GetRequestId() string {
|
|||
// only mirrors what the Edge service exposes and must not carry Node address,
|
||||
// token, or scheduling fields.
|
||||
type EdgeNodeSnapshot struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
||||
Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"`
|
||||
Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"`
|
||||
Connected bool `protobuf:"varint,4,opt,name=connected,proto3" json:"connected,omitempty"`
|
||||
Config *NodeConfigPayload `protobuf:"bytes,5,opt,name=config,proto3" json:"config,omitempty"`
|
||||
ProviderSnapshots []*ProviderSnapshot `protobuf:"bytes,6,rep,name=provider_snapshots,json=providerSnapshots,proto3" json:"provider_snapshots,omitempty"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
|
||||
Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"`
|
||||
Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"`
|
||||
Connected bool `protobuf:"varint,4,opt,name=connected,proto3" json:"connected,omitempty"`
|
||||
Config *NodeConfigPayload `protobuf:"bytes,5,opt,name=config,proto3" json:"config,omitempty"`
|
||||
// provider_snapshots uses the runtime ProviderSnapshot wire name for node
|
||||
// resources/providers. category distinguishes CLI/API/local inference.
|
||||
ProviderSnapshots []*ProviderSnapshot `protobuf:"bytes,6,rep,name=provider_snapshots,json=providerSnapshots,proto3" json:"provider_snapshots,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
|
|
|||
|
|
@ -970,6 +970,10 @@ func (x *NodeCommandResponse) GetProviderSnapshots() []*ProviderSnapshot {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ProviderSnapshot is the legacy wire name for a node resource/provider status
|
||||
// snapshot. category identifies whether the resource is API, CLI, or local
|
||||
// inference; provider-pool dispatch is limited to catalog resources referenced
|
||||
// by models[].providers.
|
||||
type ProviderSnapshot struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Adapter string `protobuf:"bytes,1,opt,name=adapter,proto3" json:"adapter,omitempty"`
|
||||
|
|
@ -2106,10 +2110,12 @@ func (x *OpenAICompatAdapterConfig) GetRequestTimeoutMs() int32 {
|
|||
return 0
|
||||
}
|
||||
|
||||
// NodeRuntimeConfig is the runtime tuning pushed to the node.
|
||||
// NodeRuntimeConfig carries legacy node runtime metadata. Execution admission
|
||||
// must not use this as a node-wide global gate; provider/resource capacity owns
|
||||
// concurrency.
|
||||
type NodeRuntimeConfig struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Concurrency int32 `protobuf:"varint,1,opt,name=concurrency,proto3" json:"concurrency,omitempty"`
|
||||
Concurrency int32 `protobuf:"varint,1,opt,name=concurrency,proto3" json:"concurrency,omitempty"` // legacy compatibility, 0/unset means no node-wide limit
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ message EdgeNodeSnapshot {
|
|||
string label = 3;
|
||||
bool connected = 4;
|
||||
NodeConfigPayload config = 5;
|
||||
// provider_snapshots uses the runtime ProviderSnapshot wire name for node
|
||||
// resources/providers. category distinguishes CLI/API/local inference.
|
||||
repeated ProviderSnapshot provider_snapshots = 6;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,10 @@ message NodeCommandResponse {
|
|||
repeated ProviderSnapshot provider_snapshots = 9;
|
||||
}
|
||||
|
||||
// ProviderSnapshot is the legacy wire name for a node resource/provider status
|
||||
// snapshot. category identifies whether the resource is API, CLI, or local
|
||||
// inference; provider-pool dispatch is limited to catalog resources referenced
|
||||
// by models[].providers.
|
||||
message ProviderSnapshot {
|
||||
string adapter = 1;
|
||||
string status = 2; // unknown|available|unavailable
|
||||
|
|
@ -248,9 +252,11 @@ message OpenAICompatAdapterConfig {
|
|||
int32 request_timeout_ms = 7;
|
||||
}
|
||||
|
||||
// NodeRuntimeConfig is the runtime tuning pushed to the node.
|
||||
// NodeRuntimeConfig carries legacy node runtime metadata. Execution admission
|
||||
// must not use this as a node-wide global gate; provider/resource capacity owns
|
||||
// concurrency.
|
||||
message NodeRuntimeConfig {
|
||||
int32 concurrency = 1;
|
||||
int32 concurrency = 1; // legacy compatibility, 0/unset means no node-wide limit
|
||||
reserved 2;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue