feat(edge): provider availability capacity queue foundation

- Move task artifacts to archive (PLAN, CODE_REVIEW, complete.log)
- Update roadmap milestone documentation
- Implement edge node snapshot and status provider
- Add control plane connector for edge commands
- Update proto definitions for control and runtime
This commit is contained in:
toki 2026-06-14 17:19:06 +09:00
parent 0e02958684
commit 04c99810ca
14 changed files with 532 additions and 181 deletions

View file

@ -39,8 +39,8 @@ Ollama, Lemonade, vLLM, SGLang 같은 provider별 상태 확인 방식 차이를
Provider별 probe 차이를 Node 내부로 감추고 Edge가 라우팅 입력으로 사용할 수 있는 최소 상태 snapshot을 정의한다.
- [x] [status-model] Node가 Edge에 노출하는 provider 상태 모델은 `unknown`, `available`, `unavailable`로 제한하고, provider가 직접 보장하지 않는 `degraded`, `starting`, `draining` 같은 상태는 넣지 않는다.
- [ ] [probe-contract] Ollama, Lemonade, vLLM, SGLang 등 provider adapter가 endpoint 생존 여부와 target/model 사용 가능 여부를 확인하는 공통 probe 인터페이스를 구현한다.
- [ ] [edge-snapshot] Edge가 provider별 구현 세부를 모르고 Node의 provider 상태, capacity, in-flight, queued 값을 조회하거나 이벤트로 받을 수 있는 계약을 정리한다.
- [x] [probe-contract] Ollama, Lemonade, vLLM, SGLang 등 provider adapter가 endpoint 생존 여부와 target/model 사용 가능 여부를 확인하는 공통 probe 인터페이스를 구현한다.
- [x] [edge-snapshot] Edge가 provider별 구현 세부를 모르고 Node의 provider 상태, capacity, in-flight, queued 값을 조회하거나 이벤트로 받을 수 있는 계약을 정리한다.
### Epic: [capacity-queue] Capacity Gate and FIFO Queue
@ -56,7 +56,7 @@ Provider별 동시 처리 한도를 IOP가 소유하고, 한도를 넘는 요청
- 상태: 없음
- 요청일: 없음
- 완료 근거: 모든 기능 Task가 아직 충족되지 않았다.
- 완료 근거: `status-model`, `probe-contract`, `edge-snapshot`은 완료되었으나 capacity queue Task가 아직 충족되지 않았다.
- 리뷰 필요:
- [ ] 사용자가 완료 결과를 확인했다
- [ ] archive 이동을 승인했다
@ -78,8 +78,8 @@ Provider별 동시 처리 한도를 IOP가 소유하고, 한도를 넘는 요청
- 표준선(선택): capacity와 queue는 provider 기능이 아니라 Node의 공통 provider execution wrapper 책임으로 둔다.
- 표준선(선택): Edge routing은 provider별 endpoint가 아니라 Node가 제공하는 availability/load snapshot을 입력으로 삼는다.
- 진행 근거: `status-model``apps/node/internal/runtime/types.go`와 Node `CAPABILITIES` 응답에 제한 상태 모델을 추가했고 `go test ./apps/node/...`로 검증했다.
- 활성 plan: `agent-task/m-provider-availability-capacity-queue-foundation/01_probe_contract/PLAN-local-G05.md``probe-contract`를 다룬다.
- 활성 plan: `agent-task/m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot/PLAN-cloud-G06.md``edge-snapshot`을 다루며 `01_probe_contract` 완료에 의존한다.
- 완료 근거: `probe-contract``agent-task/archive/2026/06/m-provider-availability-capacity-queue-foundation/01_probe_contract/complete.log`에서 PASS 완료되었고 `go test -count=1 ./apps/node/...`, `./scripts/e2e-smoke.sh`, `git diff --check`로 검증했다.
- 완료 근거: `edge-snapshot``agent-task/archive/2026/06/m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot/complete.log`에서 PASS 완료되었고 `make proto`, `go test -count=1 ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...`, `make test-control-plane-edge-wire`, `./scripts/e2e-smoke.sh`, `git diff --check`로 검증했다.
- 선행 작업: Node 단일 통로 멀티 타겟 서빙 기반, OpenAI Workspace Agent Execution Contract
- 후속 작업: Lemonade provider 서빙 경로 추가, vLLM provider 서빙 경로 추가, SGLang provider 서빙 경로 추가, model group alias와 capacity-aware routing Milestone
- 확인 필요: 없음

View file

@ -42,45 +42,46 @@ task=m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot, plan
| 항목 | 완료 여부 |
|------|---------|
| [API-1] Typed Provider Snapshot Proto | [ ] |
| [API-2] Node Snapshot Population | [ ] |
| [API-3] Edge Service and Status Propagation | [ ] |
| [API-1] Typed Provider Snapshot Proto | [x] |
| [API-2] Node Snapshot Population | [x] |
| [API-3] Edge Service and Status Propagation | [x] |
## 구현 체크리스트
- [ ] `01_probe_contract` predecessor의 `complete.log`가 있는지 확인하고 없으면 구현을 시작하지 않는다.
- [ ] provider snapshot typed proto를 추가하고 `make proto`로 Go generated files를 갱신한다.
- [ ] Node `CAPABILITIES` response가 status, capacity, in-flight, queued를 typed snapshot과 result map 양쪽에 일관되게 담도록 구현한다.
- [ ] Edge service DTO와 command path가 typed provider snapshot을 보존하도록 구현한다.
- [ ] Control Plane Edge status 또는 Edge-owned status surface가 provider snapshots를 노출하도록 구현한다.
- [ ] `go test ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
- [x] `01_probe_contract` predecessor의 `complete.log`가 있는지 확인하고 없으면 구현을 시작하지 않는다.
- [x] provider snapshot typed proto를 추가하고 `make proto`로 Go generated files를 갱신한다.
- [x] Node `CAPABILITIES` response가 status, capacity, in-flight, queued를 typed snapshot과 result map 양쪽에 일관되게 담도록 구현한다.
- [x] Edge service DTO와 command path가 typed provider snapshot을 보존하도록 구현한다.
- [x] Control Plane Edge status 또는 Edge-owned status surface가 provider snapshots를 노출하도록 구현한다.
- [x] `go test ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...`를 실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G06_N.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G06_M.log`로 아카이브한다.
- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot/`를 `agent-task/archive/YYYY/MM/m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-provider-availability-capacity-queue-foundation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G06_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G06_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot/`를 `agent-task/archive/YYYY/MM/m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-provider-availability-capacity-queue-foundation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G06.md`와 `CODE_REVIEW-cloud-G06.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
- `EdgeNodeSnapshot`의 `Config` 필드 누출 방지 정책(Non-Leak)을 만족하기 위해, `buildStatusResponse`에서 `Config` 필드 할당을 `s.Config`가 아닌 기존과 동일하게 `nil`로 설정해 보안 유출(leak)을 방지하였습니다.
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
- Node 내부에서 실행 중인 특정 어댑터의 `in_flight` 동시성 수를 정확히 조회하기 위해, `Node` 구조체 내부의 `adapterPermits` 맵과 `adapterPermitsMu` 뮤텍스를 안전하게 락킹하여 해당 어댑터 키의 `permitManager`가 보유한 `activeCount()` 값을 획득하도록 구현했습니다.
- capacity-queue 구현 전 기준선에 따라 `queued` 값을 `0`으로 고정 반환하도록 처리하였습니다.
## 사용자 리뷰 요청
@ -114,25 +115,77 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후
### API-1 중간 검증
```bash
$ make proto
(output)
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
```
### API-2 중간 검증
```bash
$ go test ./apps/node/internal/node
(output)
=== RUN TestOnCommandRequest_Capabilities
--- PASS: TestOnCommandRequest_Capabilities (0.00s)
=== RUN TestOnCommandRequest_Capabilities_InFlight
--- PASS: TestOnCommandRequest_Capabilities_InFlight (0.00s)
PASS
ok iop/apps/node/internal/node 0.128s
```
### API-3 중간 검증
```bash
$ go test ./apps/edge/internal/service ./apps/edge/internal/controlplane ./apps/edge/internal/transport
(output)
=== RUN TestServiceCapabilitiesPreservesProviderSnapshots
--- PASS: TestServiceCapabilitiesPreservesProviderSnapshots (0.00s)
PASS
ok iop/apps/edge/internal/service (cached)
=== RUN TestConnectorRespondsToStatusRequestFromProvider
--- PASS: TestConnectorRespondsToStatusRequestFromProvider (0.00s)
=== RUN TestConnectorBuildStatusResponseNonLeak
--- PASS: TestConnectorBuildStatusResponseNonLeak (0.00s)
PASS
ok iop/apps/edge/internal/controlplane 4.448s
=== RUN TestEdgeParserMap_NodeCommandResponse_NewTypes
--- PASS: TestEdgeParserMap_NodeCommandResponse_NewTypes (0.00s)
PASS
ok iop/apps/edge/internal/transport 2.012s
```
### 최종 검증
```bash
$ go test ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...
(output)
ok iop/apps/node/cmd/node 0.015s
ok iop/apps/node/internal/adapters 0.013s
ok iop/apps/node/internal/adapters/cli 46.743s
ok iop/apps/node/internal/adapters/cli/status (cached)
ok iop/apps/node/internal/adapters/ollama (cached)
ok iop/apps/node/internal/adapters/vllm (cached)
ok iop/apps/node/internal/bootstrap 0.270s
ok iop/apps/node/internal/node 0.128s
ok iop/apps/node/internal/router 0.006s
ok iop/apps/node/internal/store (cached)
ok iop/apps/node/internal/terminal 0.595s
ok iop/apps/node/internal/transport 5.145s
ok iop/apps/edge/cmd/edge 0.033s
ok iop/apps/edge/internal/bootstrap 0.016s
ok iop/apps/edge/internal/controlplane 4.448s
ok iop/apps/edge/internal/edgecmd 0.009s
ok iop/apps/edge/internal/events 0.004s
ok iop/apps/edge/internal/input 0.005s
ok iop/apps/edge/internal/input/a2a 0.009s
ok iop/apps/edge/internal/node 0.004s
ok iop/apps/edge/internal/openai 1.507s
ok iop/apps/edge/internal/opsconsole 0.006s
ok iop/apps/edge/internal/service 0.005s
ok iop/apps/edge/internal/transport 2.012s
ok iop/packages/go/audit (cached)
ok iop/packages/go/config (cached)
ok iop/packages/go/hostsetup (cached)
ok iop/packages/go/observability (cached)
```
---
@ -150,3 +203,23 @@ $ go test ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless user-only blocker exists |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus |
| 검증 결과 | Implementing agent | Paste actual output |
## 코드리뷰 결과
- 종합 판정: PASS
- 차원별 평가:
- correctness: Pass
- completeness: Pass
- test coverage: Pass
- API contract: Pass
- code quality: Pass
- plan deviation: Pass
- verification trust: Pass
- 발견된 문제: 없음
- 리뷰어 검증:
- `make proto` PASS
- `go test -count=1 ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...` PASS
- `make test-control-plane-edge-wire` PASS
- `./scripts/e2e-smoke.sh` PASS
- `git diff --check` PASS
- 다음 단계: PASS이므로 `complete.log` 작성 후 task directory를 archive로 이동한다.

View file

@ -0,0 +1,45 @@
# Complete - m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot
## 완료 일시
2026-06-14
## 요약
Edge provider snapshot typed contract 구현 리뷰 1회차를 PASS로 종결했다.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_cloud_G06_0.log` | `code_review_cloud_G06_0.log` | PASS | provider snapshot proto, Edge service command DTO 보존, Control Plane status snapshot 전달, 관련 테스트와 보조 smoke 검증 통과 |
## 구현/정리 내용
- `ProviderSnapshot` proto를 추가하고 `NodeCommandResponse` 및 `EdgeNodeSnapshot`에 typed provider snapshot 목록을 연결했다.
- Edge service `NodeCommandView`가 node command response의 typed provider snapshots를 보존하도록 했다.
- Control Plane connector status response가 Edge-owned `NodeSnapshot.ProviderSnapshots`를 wire snapshot에 복사하도록 했다.
- 관련 service/controlplane/transport 테스트에서 provider snapshot 보존과 protobuf parser round-trip을 검증했다.
## 최종 검증
- `make proto` - PASS; Go protobuf 생성물을 proto 원본에서 재생성했다.
- `go test -count=1 ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...` - PASS; cache 없이 node, edge, platform-common, generated proto 패키지 검증을 통과했다.
- `make test-control-plane-edge-wire` - PASS; 실제 Control Plane/Edge 프로세스로 hello accepted, Edge connected, disconnect marker를 확인했다.
- `./scripts/e2e-smoke.sh` - PASS; mock profile에서 `scripts/dev/edge.sh`와 `scripts/dev/node.sh`를 임시 config로 구동하고 `/nodes`, `/capabilities`, `/transport`, 메시지 2회 relay, `/sessions`, `/terminate-session`을 확인했다.
- `git diff --check` - PASS; whitespace 오류 없음.
## Roadmap Completion
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md`
- Completed task ids:
- `edge-snapshot`: PASS; evidence=`agent-task/archive/2026/06/m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot/plan_cloud_G06_0.log`, `agent-task/archive/2026/06/m-provider-availability-capacity-queue-foundation/02+01_edge_snapshot/code_review_cloud_G06_0.log`; verification=`make proto`, `go test -count=1 ./apps/node/... ./apps/edge/... ./packages/go/... ./proto/gen/...`, `make test-control-plane-edge-wire`, `./scripts/e2e-smoke.sh`, `git diff --check`
- Not completed task ids: 없음
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -365,11 +365,12 @@ func (c *Connector) buildStatusResponse(req *iop.EdgeStatusRequest) *iop.EdgeSta
nodes := make([]*iop.EdgeNodeSnapshot, 0, len(snaps))
for _, s := range snaps {
nodes = append(nodes, &iop.EdgeNodeSnapshot{
NodeId: s.NodeID,
Alias: s.Alias,
Label: s.Label,
Connected: true,
Config: nil,
NodeId: s.NodeID,
Alias: s.Alias,
Label: s.Label,
Connected: true,
Config: nil,
ProviderSnapshots: s.ProviderSnapshots,
})
}
resp.Nodes = nodes

View file

@ -220,7 +220,20 @@ func startFakeCPServerForNodeEvents(t *testing.T) (int, <-chan *iop.EdgeNodeEven
// boundary rather than a direct registry/transport read.
func TestConnectorRespondsToStatusRequestFromProvider(t *testing.T) {
provider := fakeStatusProvider{nodes: []edgeservice.NodeSnapshot{
{NodeID: "node-1", Alias: "alpha", Label: "node0"},
{
NodeID: "node-1",
Alias: "alpha",
Label: "node0",
ProviderSnapshots: []*iop.ProviderSnapshot{
{
Adapter: "cli",
Status: "available",
Capacity: 4,
InFlight: 2,
Queued: 0,
},
},
},
{NodeID: "node-2", Label: "node1"},
}}
@ -279,6 +292,14 @@ func TestConnectorRespondsToStatusRequestFromProvider(t *testing.T) {
}
if n := byID["node-1"]; n == nil || n.GetAlias() != "alpha" || n.GetLabel() != "node0" {
t.Errorf("node-1 snapshot: %+v", n)
} else {
if len(n.GetProviderSnapshots()) != 1 {
t.Fatalf("expected 1 provider snapshot, got %d", len(n.GetProviderSnapshots()))
}
snap := n.GetProviderSnapshots()[0]
if snap.GetAdapter() != "cli" || snap.GetStatus() != "available" || snap.GetCapacity() != 4 || snap.GetInFlight() != 2 || snap.GetQueued() != 0 {
t.Errorf("unexpected snapshot contents: %+v", snap)
}
}
if n := byID["node-2"]; n == nil || n.GetLabel() != "node1" {
t.Errorf("node-2 snapshot: %+v", n)

View file

@ -105,13 +105,14 @@ type NodeCommandRequestSpec struct {
// commands. Result mirrors the proto map and is empty when the node returned
// no payload.
type NodeCommandView struct {
NodeID string
NodeLabel string
Adapter string
Target string
SessionID string
Type iop.NodeCommandType
Result map[string]string
NodeID string
NodeLabel string
Adapter string
Target string
SessionID string
Type iop.NodeCommandType
Result map[string]string
ProviderSnapshots []*iop.ProviderSnapshot
}
// Capabilities dispatches a CAPABILITIES node command and returns the result map.
@ -196,12 +197,13 @@ func (s *Service) sendNodeCommand(req NodeCommandRequestSpec, cmdType iop.NodeCo
return NodeCommandView{}, fmt.Errorf("node reported error: %s", resp.GetError())
}
return NodeCommandView{
NodeID: entry.NodeID,
NodeLabel: nodeLabel(entry),
Adapter: commandReq.GetAdapter(),
Target: commandReq.GetTarget(),
SessionID: commandReq.GetSessionId(),
Type: resp.GetType(),
Result: resp.GetResult(),
NodeID: entry.NodeID,
NodeLabel: nodeLabel(entry),
Adapter: commandReq.GetAdapter(),
Target: commandReq.GetTarget(),
SessionID: commandReq.GetSessionId(),
Type: resp.GetType(),
Result: resp.GetResult(),
ProviderSnapshots: resp.GetProviderSnapshots(),
}, nil
}

View file

@ -873,3 +873,70 @@ func TestServiceExecuteCommandAgentCommandCapabilitiesNodeError(t *testing.T) {
t.Errorf("unexpected event summary: got %q, expected %q", events[len(events)-1].Summary, expectedSummary)
}
}
func TestServiceCapabilitiesPreservesProviderSnapshots(t *testing.T) {
edgeConn, nodeConn := net.Pipe()
defer edgeConn.Close()
defer nodeConn.Close()
parserMap := toki.ParserMap{
toki.TypeNameOf(&iop.NodeCommandRequest{}): func(b []byte) (proto.Message, error) {
m := &iop.NodeCommandRequest{}
return m, proto.Unmarshal(b, m)
},
toki.TypeNameOf(&iop.NodeCommandResponse{}): func(b []byte) (proto.Message, error) {
m := &iop.NodeCommandResponse{}
return m, proto.Unmarshal(b, m)
},
}
edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)
toki.AddRequestListenerTyped(&nodeClient.Communicator, func(req *iop.NodeCommandRequest) (*iop.NodeCommandResponse, error) {
if req.GetType() != iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES {
return nil, fmt.Errorf("unexpected command type: %v", req.GetType())
}
return &iop.NodeCommandResponse{
RequestId: req.RequestId,
Type: req.Type,
Result: map[string]string{"caps": "cli,codex"},
ProviderSnapshots: []*iop.ProviderSnapshot{
{
Adapter: "cli",
Status: "available",
Capacity: 4,
InFlight: 2,
Queued: 0,
},
},
}, nil
})
reg := edgenode.NewRegistry()
reg.Register(&edgenode.NodeEntry{
NodeID: "node-caps-1",
Alias: "deployer",
AgentKind: config.AgentKindGenericNode,
Client: edgeClient,
})
svc := edgeservice.New(reg, nil)
view, err := svc.Capabilities(context.Background(), edgeservice.NodeCommandRequestSpec{
NodeRef: "deployer",
Adapter: "cli",
Target: "codex",
SessionID: "sess-caps",
})
if err != nil {
t.Fatalf("Capabilities: %v", err)
}
if len(view.ProviderSnapshots) != 1 {
t.Fatalf("expected 1 provider snapshot, got %d", len(view.ProviderSnapshots))
}
snap := view.ProviderSnapshots[0]
if snap.Adapter != "cli" || snap.Status != "available" || snap.Capacity != 4 || snap.InFlight != 2 || snap.Queued != 0 {
t.Fatalf("unexpected snapshot contents: %+v", snap)
}
}

View file

@ -10,12 +10,13 @@ import (
// AgentKind and LifecycleState carry the registry entry's generic node
// classification and lifecycle.
type NodeSnapshot struct {
NodeID string
Alias string
Label string
AgentKind string
LifecycleState string
Config *iop.NodeConfigPayload
NodeID string
Alias string
Label string
AgentKind string
LifecycleState string
Config *iop.NodeConfigPayload
ProviderSnapshots []*iop.ProviderSnapshot
}
// ListNodeSnapshots returns surface-neutral node descriptors derived from the

View file

@ -50,21 +50,35 @@ func TestEdgeParserMap_NodeCommandResponse(t *testing.T) {
func TestEdgeParserMap_NodeCommandResponse_NewTypes(t *testing.T) {
parsers := edgeParserMap()
cases := []struct {
cmdType iop.NodeCommandType
result map[string]string
cmdType iop.NodeCommandType
result map[string]string
providerSnapshots []*iop.ProviderSnapshot
}{
{iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES, map[string]string{"targets": "codex,antigravity"}},
{iop.NodeCommandType_NODE_COMMAND_TYPE_SESSION_LIST, map[string]string{"count": "2"}},
{iop.NodeCommandType_NODE_COMMAND_TYPE_TRANSPORT_STATUS, map[string]string{"connected": "true"}},
{
cmdType: iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
result: map[string]string{"targets": "codex,antigravity"},
providerSnapshots: []*iop.ProviderSnapshot{
{
Adapter: "cli",
Status: "available",
Capacity: 4,
InFlight: 2,
Queued: 0,
},
},
},
{iop.NodeCommandType_NODE_COMMAND_TYPE_SESSION_LIST, map[string]string{"count": "2"}, nil},
{iop.NodeCommandType_NODE_COMMAND_TYPE_TRANSPORT_STATUS, map[string]string{"connected": "true"}, nil},
}
for _, tc := range cases {
original := &iop.NodeCommandResponse{
RequestId: "req-1",
Type: tc.cmdType,
Adapter: "cli",
Target: "codex",
SessionId: "default",
Result: tc.result,
RequestId: "req-1",
Type: tc.cmdType,
Adapter: "cli",
Target: "codex",
SessionId: "default",
Result: tc.result,
ProviderSnapshots: tc.providerSnapshots,
}
payload, err := proto.Marshal(original)
if err != nil {
@ -87,6 +101,19 @@ func TestEdgeParserMap_NodeCommandResponse_NewTypes(t *testing.T) {
t.Fatalf("result[%q]: got %q want %q", k, got.GetResult()[k], v)
}
}
if len(got.GetProviderSnapshots()) != len(tc.providerSnapshots) {
t.Fatalf("provider snapshots count: got %d want %d", len(got.GetProviderSnapshots()), len(tc.providerSnapshots))
}
for i, snap := range tc.providerSnapshots {
g := got.GetProviderSnapshots()[i]
if g.GetAdapter() != snap.GetAdapter() ||
g.GetStatus() != snap.GetStatus() ||
g.GetCapacity() != snap.GetCapacity() ||
g.GetInFlight() != snap.GetInFlight() ||
g.GetQueued() != snap.GetQueued() {
t.Fatalf("provider snapshot mismatch at index %d: %+v vs %+v", i, g, snap)
}
}
}
}

View file

@ -490,14 +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"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EdgeNodeSnapshot) Reset() {
@ -565,6 +566,13 @@ func (x *EdgeNodeSnapshot) GetConfig() *NodeConfigPayload {
return nil
}
func (x *EdgeNodeSnapshot) GetProviderSnapshots() []*ProviderSnapshot {
if x != nil {
return x.ProviderSnapshots
}
return nil
}
// EdgeStatusResponse returns the Edge-owned node snapshot list in reply to an
// EdgeStatusRequest. The Control Plane observes this snapshot rather than
// replicating the Edge-local registry.
@ -1098,13 +1106,14 @@ const file_proto_iop_control_proto_rawDesc = "" +
"\x06reason\x18\x05 \x01(\tR\x06reason\"2\n" +
"\x11EdgeStatusRequest\x12\x1d\n" +
"\n" +
"request_id\x18\x01 \x01(\tR\trequestId\"\xa5\x01\n" +
"request_id\x18\x01 \x01(\tR\trequestId\"\xeb\x01\n" +
"\x10EdgeNodeSnapshot\x12\x17\n" +
"\anode_id\x18\x01 \x01(\tR\x06nodeId\x12\x14\n" +
"\x05alias\x18\x02 \x01(\tR\x05alias\x12\x14\n" +
"\x05label\x18\x03 \x01(\tR\x05label\x12\x1c\n" +
"\tconnected\x18\x04 \x01(\bR\tconnected\x12.\n" +
"\x06config\x18\x05 \x01(\v2\x16.iop.NodeConfigPayloadR\x06config\"\xe5\x03\n" +
"\x06config\x18\x05 \x01(\v2\x16.iop.NodeConfigPayloadR\x06config\x12D\n" +
"\x12provider_snapshots\x18\x06 \x03(\v2\x15.iop.ProviderSnapshotR\x11providerSnapshots\"\xe5\x03\n" +
"\x12EdgeStatusResponse\x12\x1d\n" +
"\n" +
"request_id\x18\x01 \x01(\tR\trequestId\x12\x17\n" +
@ -1196,21 +1205,23 @@ var file_proto_iop_control_proto_goTypes = []any{
nil, // 17: iop.EdgeStatusResponse.MetadataEntry
nil, // 18: iop.EdgeCommandRequest.ParametersEntry
(*NodeConfigPayload)(nil), // 19: iop.NodeConfigPayload
(*ProviderSnapshot)(nil), // 20: iop.ProviderSnapshot
}
var file_proto_iop_control_proto_depIdxs = []int32{
15, // 0: iop.PolicyRule.params:type_name -> iop.PolicyRule.ParamsEntry
16, // 1: iop.EdgeHelloRequest.metadata:type_name -> iop.EdgeHelloRequest.MetadataEntry
19, // 2: iop.EdgeNodeSnapshot.config:type_name -> iop.NodeConfigPayload
8, // 3: iop.EdgeStatusResponse.nodes:type_name -> iop.EdgeNodeSnapshot
17, // 4: iop.EdgeStatusResponse.metadata:type_name -> iop.EdgeStatusResponse.MetadataEntry
10, // 5: iop.EdgeStatusResponse.capabilities:type_name -> iop.EdgeCapabilitySummary
11, // 6: iop.EdgeStatusResponse.domain_agents:type_name -> iop.EdgeDomainAgentSummary
18, // 7: iop.EdgeCommandRequest.parameters:type_name -> iop.EdgeCommandRequest.ParametersEntry
8, // [8:8] is the sub-list for method output_type
8, // [8:8] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
20, // 3: iop.EdgeNodeSnapshot.provider_snapshots:type_name -> iop.ProviderSnapshot
8, // 4: iop.EdgeStatusResponse.nodes:type_name -> iop.EdgeNodeSnapshot
17, // 5: iop.EdgeStatusResponse.metadata:type_name -> iop.EdgeStatusResponse.MetadataEntry
10, // 6: iop.EdgeStatusResponse.capabilities:type_name -> iop.EdgeCapabilitySummary
11, // 7: iop.EdgeStatusResponse.domain_agents:type_name -> iop.EdgeDomainAgentSummary
18, // 8: iop.EdgeCommandRequest.parameters:type_name -> iop.EdgeCommandRequest.ParametersEntry
9, // [9:9] is the sub-list for method output_type
9, // [9:9] is the sub-list for method input_type
9, // [9:9] is the sub-list for extension type_name
9, // [9:9] is the sub-list for extension extendee
0, // [0:9] is the sub-list for field type_name
}
func init() { file_proto_iop_control_proto_init() }

View file

@ -816,9 +816,10 @@ type NodeCommandResponse struct {
// result carries free-form key/value data for non-usage-status command types
// (capabilities, session_list, transport_status). usage_status keeps its
// dedicated typed payload.
Result map[string]string `protobuf:"bytes,8,rep,name=result,proto3" json:"result,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Result map[string]string `protobuf:"bytes,8,rep,name=result,proto3" json:"result,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
ProviderSnapshots []*ProviderSnapshot `protobuf:"bytes,9,rep,name=provider_snapshots,json=providerSnapshots,proto3" json:"provider_snapshots,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *NodeCommandResponse) Reset() {
@ -907,6 +908,89 @@ func (x *NodeCommandResponse) GetResult() map[string]string {
return nil
}
func (x *NodeCommandResponse) GetProviderSnapshots() []*ProviderSnapshot {
if x != nil {
return x.ProviderSnapshots
}
return nil
}
type ProviderSnapshot struct {
state protoimpl.MessageState `protogen:"open.v1"`
Adapter string `protobuf:"bytes,1,opt,name=adapter,proto3" json:"adapter,omitempty"`
Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` // unknown|available|unavailable
Capacity int32 `protobuf:"varint,3,opt,name=capacity,proto3" json:"capacity,omitempty"`
InFlight int32 `protobuf:"varint,4,opt,name=in_flight,json=inFlight,proto3" json:"in_flight,omitempty"`
Queued int32 `protobuf:"varint,5,opt,name=queued,proto3" json:"queued,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProviderSnapshot) Reset() {
*x = ProviderSnapshot{}
mi := &file_proto_iop_runtime_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProviderSnapshot) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProviderSnapshot) ProtoMessage() {}
func (x *ProviderSnapshot) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[8]
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 ProviderSnapshot.ProtoReflect.Descriptor instead.
func (*ProviderSnapshot) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{8}
}
func (x *ProviderSnapshot) GetAdapter() string {
if x != nil {
return x.Adapter
}
return ""
}
func (x *ProviderSnapshot) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
func (x *ProviderSnapshot) GetCapacity() int32 {
if x != nil {
return x.Capacity
}
return 0
}
func (x *ProviderSnapshot) GetInFlight() int32 {
if x != nil {
return x.InFlight
}
return 0
}
func (x *ProviderSnapshot) GetQueued() int32 {
if x != nil {
return x.Queued
}
return 0
}
type AgentUsageStatus struct {
state protoimpl.MessageState `protogen:"open.v1"`
RawOutput string `protobuf:"bytes,1,opt,name=raw_output,json=rawOutput,proto3" json:"raw_output,omitempty"`
@ -921,7 +1005,7 @@ type AgentUsageStatus struct {
func (x *AgentUsageStatus) Reset() {
*x = AgentUsageStatus{}
mi := &file_proto_iop_runtime_proto_msgTypes[8]
mi := &file_proto_iop_runtime_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -933,7 +1017,7 @@ func (x *AgentUsageStatus) String() string {
func (*AgentUsageStatus) ProtoMessage() {}
func (x *AgentUsageStatus) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[8]
mi := &file_proto_iop_runtime_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -946,7 +1030,7 @@ func (x *AgentUsageStatus) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentUsageStatus.ProtoReflect.Descriptor instead.
func (*AgentUsageStatus) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{8}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{9}
}
func (x *AgentUsageStatus) GetRawOutput() string {
@ -1002,7 +1086,7 @@ type Error struct {
func (x *Error) Reset() {
*x = Error{}
mi := &file_proto_iop_runtime_proto_msgTypes[9]
mi := &file_proto_iop_runtime_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1014,7 +1098,7 @@ func (x *Error) String() string {
func (*Error) ProtoMessage() {}
func (x *Error) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[9]
mi := &file_proto_iop_runtime_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1027,7 +1111,7 @@ func (x *Error) ProtoReflect() protoreflect.Message {
// Deprecated: Use Error.ProtoReflect.Descriptor instead.
func (*Error) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{9}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{10}
}
func (x *Error) GetCode() string {
@ -1054,7 +1138,7 @@ type RegisterRequest struct {
func (x *RegisterRequest) Reset() {
*x = RegisterRequest{}
mi := &file_proto_iop_runtime_proto_msgTypes[10]
mi := &file_proto_iop_runtime_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1066,7 +1150,7 @@ func (x *RegisterRequest) String() string {
func (*RegisterRequest) ProtoMessage() {}
func (x *RegisterRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[10]
mi := &file_proto_iop_runtime_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1079,7 +1163,7 @@ func (x *RegisterRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RegisterRequest.ProtoReflect.Descriptor instead.
func (*RegisterRequest) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{10}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{11}
}
func (x *RegisterRequest) GetToken() string {
@ -1103,7 +1187,7 @@ type RegisterResponse struct {
func (x *RegisterResponse) Reset() {
*x = RegisterResponse{}
mi := &file_proto_iop_runtime_proto_msgTypes[11]
mi := &file_proto_iop_runtime_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1115,7 +1199,7 @@ func (x *RegisterResponse) String() string {
func (*RegisterResponse) ProtoMessage() {}
func (x *RegisterResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[11]
mi := &file_proto_iop_runtime_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1128,7 +1212,7 @@ func (x *RegisterResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use RegisterResponse.ProtoReflect.Descriptor instead.
func (*RegisterResponse) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{11}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{12}
}
func (x *RegisterResponse) GetAccepted() bool {
@ -1177,7 +1261,7 @@ type NodeConfigPayload struct {
func (x *NodeConfigPayload) Reset() {
*x = NodeConfigPayload{}
mi := &file_proto_iop_runtime_proto_msgTypes[12]
mi := &file_proto_iop_runtime_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1189,7 +1273,7 @@ func (x *NodeConfigPayload) String() string {
func (*NodeConfigPayload) ProtoMessage() {}
func (x *NodeConfigPayload) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[12]
mi := &file_proto_iop_runtime_proto_msgTypes[13]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1202,7 +1286,7 @@ func (x *NodeConfigPayload) ProtoReflect() protoreflect.Message {
// Deprecated: Use NodeConfigPayload.ProtoReflect.Descriptor instead.
func (*NodeConfigPayload) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{12}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{13}
}
func (x *NodeConfigPayload) GetAdapters() []*AdapterConfig {
@ -1243,7 +1327,7 @@ type AdapterConfig struct {
func (x *AdapterConfig) Reset() {
*x = AdapterConfig{}
mi := &file_proto_iop_runtime_proto_msgTypes[13]
mi := &file_proto_iop_runtime_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1255,7 +1339,7 @@ func (x *AdapterConfig) String() string {
func (*AdapterConfig) ProtoMessage() {}
func (x *AdapterConfig) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[13]
mi := &file_proto_iop_runtime_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1268,7 +1352,7 @@ func (x *AdapterConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use AdapterConfig.ProtoReflect.Descriptor instead.
func (*AdapterConfig) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{13}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{14}
}
func (x *AdapterConfig) GetType() string {
@ -1385,7 +1469,7 @@ type MockAdapterConfig struct {
func (x *MockAdapterConfig) Reset() {
*x = MockAdapterConfig{}
mi := &file_proto_iop_runtime_proto_msgTypes[14]
mi := &file_proto_iop_runtime_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1397,7 +1481,7 @@ func (x *MockAdapterConfig) String() string {
func (*MockAdapterConfig) ProtoMessage() {}
func (x *MockAdapterConfig) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[14]
mi := &file_proto_iop_runtime_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1410,7 +1494,7 @@ func (x *MockAdapterConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use MockAdapterConfig.ProtoReflect.Descriptor instead.
func (*MockAdapterConfig) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{14}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{15}
}
type CLIAdapterConfig struct {
@ -1422,7 +1506,7 @@ type CLIAdapterConfig struct {
func (x *CLIAdapterConfig) Reset() {
*x = CLIAdapterConfig{}
mi := &file_proto_iop_runtime_proto_msgTypes[15]
mi := &file_proto_iop_runtime_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1434,7 +1518,7 @@ func (x *CLIAdapterConfig) String() string {
func (*CLIAdapterConfig) ProtoMessage() {}
func (x *CLIAdapterConfig) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[15]
mi := &file_proto_iop_runtime_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1447,7 +1531,7 @@ func (x *CLIAdapterConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use CLIAdapterConfig.ProtoReflect.Descriptor instead.
func (*CLIAdapterConfig) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{15}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{16}
}
func (x *CLIAdapterConfig) GetProfiles() map[string]*CLIProfileConfig {
@ -1476,7 +1560,7 @@ type CLIProfileConfig struct {
func (x *CLIProfileConfig) Reset() {
*x = CLIProfileConfig{}
mi := &file_proto_iop_runtime_proto_msgTypes[16]
mi := &file_proto_iop_runtime_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1488,7 +1572,7 @@ func (x *CLIProfileConfig) String() string {
func (*CLIProfileConfig) ProtoMessage() {}
func (x *CLIProfileConfig) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[16]
mi := &file_proto_iop_runtime_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1501,7 +1585,7 @@ func (x *CLIProfileConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use CLIProfileConfig.ProtoReflect.Descriptor instead.
func (*CLIProfileConfig) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{16}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{17}
}
func (x *CLIProfileConfig) GetCommand() string {
@ -1591,7 +1675,7 @@ type CLICompletionMarker struct {
func (x *CLICompletionMarker) Reset() {
*x = CLICompletionMarker{}
mi := &file_proto_iop_runtime_proto_msgTypes[17]
mi := &file_proto_iop_runtime_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1603,7 +1687,7 @@ func (x *CLICompletionMarker) String() string {
func (*CLICompletionMarker) ProtoMessage() {}
func (x *CLICompletionMarker) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[17]
mi := &file_proto_iop_runtime_proto_msgTypes[18]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1616,7 +1700,7 @@ func (x *CLICompletionMarker) ProtoReflect() protoreflect.Message {
// Deprecated: Use CLICompletionMarker.ProtoReflect.Descriptor instead.
func (*CLICompletionMarker) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{17}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{18}
}
func (x *CLICompletionMarker) GetLine() string {
@ -1643,7 +1727,7 @@ type OllamaAdapterConfig struct {
func (x *OllamaAdapterConfig) Reset() {
*x = OllamaAdapterConfig{}
mi := &file_proto_iop_runtime_proto_msgTypes[18]
mi := &file_proto_iop_runtime_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1655,7 +1739,7 @@ func (x *OllamaAdapterConfig) String() string {
func (*OllamaAdapterConfig) ProtoMessage() {}
func (x *OllamaAdapterConfig) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[18]
mi := &file_proto_iop_runtime_proto_msgTypes[19]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1668,7 +1752,7 @@ func (x *OllamaAdapterConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use OllamaAdapterConfig.ProtoReflect.Descriptor instead.
func (*OllamaAdapterConfig) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{18}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{19}
}
func (x *OllamaAdapterConfig) GetBaseUrl() string {
@ -1694,7 +1778,7 @@ type VllmAdapterConfig struct {
func (x *VllmAdapterConfig) Reset() {
*x = VllmAdapterConfig{}
mi := &file_proto_iop_runtime_proto_msgTypes[19]
mi := &file_proto_iop_runtime_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1706,7 +1790,7 @@ func (x *VllmAdapterConfig) String() string {
func (*VllmAdapterConfig) ProtoMessage() {}
func (x *VllmAdapterConfig) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[19]
mi := &file_proto_iop_runtime_proto_msgTypes[20]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1719,7 +1803,7 @@ func (x *VllmAdapterConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use VllmAdapterConfig.ProtoReflect.Descriptor instead.
func (*VllmAdapterConfig) Descriptor() ([]byte, []int) {
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{19}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{20}
}
func (x *VllmAdapterConfig) GetEndpoint() string {
@ -1740,7 +1824,7 @@ type NodeRuntimeConfig struct {
func (x *NodeRuntimeConfig) Reset() {
*x = NodeRuntimeConfig{}
mi := &file_proto_iop_runtime_proto_msgTypes[20]
mi := &file_proto_iop_runtime_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1752,7 +1836,7 @@ func (x *NodeRuntimeConfig) String() string {
func (*NodeRuntimeConfig) ProtoMessage() {}
func (x *NodeRuntimeConfig) ProtoReflect() protoreflect.Message {
mi := &file_proto_iop_runtime_proto_msgTypes[20]
mi := &file_proto_iop_runtime_proto_msgTypes[21]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1765,7 +1849,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{20}
return file_proto_iop_runtime_proto_rawDescGZIP(), []int{21}
}
func (x *NodeRuntimeConfig) GetConcurrency() int32 {
@ -1867,7 +1951,7 @@ const file_proto_iop_runtime_proto_rawDesc = "" +
"\bmetadata\x18\a \x03(\v2%.iop.NodeCommandRequest.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xf8\x02\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xbe\x03\n" +
"\x13NodeCommandResponse\x12\x1d\n" +
"\n" +
"request_id\x18\x01 \x01(\tR\trequestId\x12(\n" +
@ -1878,10 +1962,17 @@ const file_proto_iop_runtime_proto_rawDesc = "" +
"session_id\x18\x05 \x01(\tR\tsessionId\x128\n" +
"\fusage_status\x18\x06 \x01(\v2\x15.iop.AgentUsageStatusR\vusageStatus\x12\x14\n" +
"\x05error\x18\a \x01(\tR\x05error\x12<\n" +
"\x06result\x18\b \x03(\v2$.iop.NodeCommandResponse.ResultEntryR\x06result\x1a9\n" +
"\x06result\x18\b \x03(\v2$.iop.NodeCommandResponse.ResultEntryR\x06result\x12D\n" +
"\x12provider_snapshots\x18\t \x03(\v2\x15.iop.ProviderSnapshotR\x11providerSnapshots\x1a9\n" +
"\vResultEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc9\x02\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x95\x01\n" +
"\x10ProviderSnapshot\x12\x18\n" +
"\aadapter\x18\x01 \x01(\tR\aadapter\x12\x16\n" +
"\x06status\x18\x02 \x01(\tR\x06status\x12\x1a\n" +
"\bcapacity\x18\x03 \x01(\x05R\bcapacity\x12\x1b\n" +
"\tin_flight\x18\x04 \x01(\x05R\binFlight\x12\x16\n" +
"\x06queued\x18\x05 \x01(\x05R\x06queued\"\xc9\x02\n" +
"\x10AgentUsageStatus\x12\x1d\n" +
"\n" +
"raw_output\x18\x01 \x01(\tR\trawOutput\x12\x1f\n" +
@ -1981,7 +2072,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, 28)
var file_proto_iop_runtime_proto_msgTypes = make([]protoimpl.MessageInfo, 29)
var file_proto_iop_runtime_proto_goTypes = []any{
(RunSessionMode)(0), // 0: iop.RunSessionMode
(CancelAction)(0), // 1: iop.CancelAction
@ -1994,59 +2085,61 @@ var file_proto_iop_runtime_proto_goTypes = []any{
(*CancelRequest)(nil), // 8: iop.CancelRequest
(*NodeCommandRequest)(nil), // 9: iop.NodeCommandRequest
(*NodeCommandResponse)(nil), // 10: iop.NodeCommandResponse
(*AgentUsageStatus)(nil), // 11: iop.AgentUsageStatus
(*Error)(nil), // 12: iop.Error
(*RegisterRequest)(nil), // 13: iop.RegisterRequest
(*RegisterResponse)(nil), // 14: iop.RegisterResponse
(*NodeConfigPayload)(nil), // 15: iop.NodeConfigPayload
(*AdapterConfig)(nil), // 16: iop.AdapterConfig
(*MockAdapterConfig)(nil), // 17: iop.MockAdapterConfig
(*CLIAdapterConfig)(nil), // 18: iop.CLIAdapterConfig
(*CLIProfileConfig)(nil), // 19: iop.CLIProfileConfig
(*CLICompletionMarker)(nil), // 20: iop.CLICompletionMarker
(*OllamaAdapterConfig)(nil), // 21: iop.OllamaAdapterConfig
(*VllmAdapterConfig)(nil), // 22: iop.VllmAdapterConfig
(*NodeRuntimeConfig)(nil), // 23: iop.NodeRuntimeConfig
nil, // 24: iop.RunRequest.MetadataEntry
nil, // 25: iop.RunEvent.MetadataEntry
nil, // 26: iop.EdgeNodeEvent.MetadataEntry
nil, // 27: iop.NodeCommandRequest.MetadataEntry
nil, // 28: iop.NodeCommandResponse.ResultEntry
nil, // 29: iop.AgentUsageStatus.MetadataEntry
nil, // 30: iop.CLIAdapterConfig.ProfilesEntry
(*structpb.Struct)(nil), // 31: google.protobuf.Struct
(*ProviderSnapshot)(nil), // 11: iop.ProviderSnapshot
(*AgentUsageStatus)(nil), // 12: iop.AgentUsageStatus
(*Error)(nil), // 13: iop.Error
(*RegisterRequest)(nil), // 14: iop.RegisterRequest
(*RegisterResponse)(nil), // 15: iop.RegisterResponse
(*NodeConfigPayload)(nil), // 16: iop.NodeConfigPayload
(*AdapterConfig)(nil), // 17: iop.AdapterConfig
(*MockAdapterConfig)(nil), // 18: iop.MockAdapterConfig
(*CLIAdapterConfig)(nil), // 19: iop.CLIAdapterConfig
(*CLIProfileConfig)(nil), // 20: iop.CLIProfileConfig
(*CLICompletionMarker)(nil), // 21: iop.CLICompletionMarker
(*OllamaAdapterConfig)(nil), // 22: iop.OllamaAdapterConfig
(*VllmAdapterConfig)(nil), // 23: iop.VllmAdapterConfig
(*NodeRuntimeConfig)(nil), // 24: iop.NodeRuntimeConfig
nil, // 25: iop.RunRequest.MetadataEntry
nil, // 26: iop.RunEvent.MetadataEntry
nil, // 27: iop.EdgeNodeEvent.MetadataEntry
nil, // 28: iop.NodeCommandRequest.MetadataEntry
nil, // 29: iop.NodeCommandResponse.ResultEntry
nil, // 30: iop.AgentUsageStatus.MetadataEntry
nil, // 31: iop.CLIAdapterConfig.ProfilesEntry
(*structpb.Struct)(nil), // 32: google.protobuf.Struct
}
var file_proto_iop_runtime_proto_depIdxs = []int32{
31, // 0: iop.RunRequest.policy:type_name -> google.protobuf.Struct
31, // 1: iop.RunRequest.input:type_name -> google.protobuf.Struct
24, // 2: iop.RunRequest.metadata:type_name -> iop.RunRequest.MetadataEntry
32, // 0: iop.RunRequest.policy:type_name -> google.protobuf.Struct
32, // 1: iop.RunRequest.input:type_name -> google.protobuf.Struct
25, // 2: iop.RunRequest.metadata:type_name -> iop.RunRequest.MetadataEntry
0, // 3: iop.RunRequest.session_mode:type_name -> iop.RunSessionMode
6, // 4: iop.RunEvent.usage:type_name -> iop.Usage
25, // 5: iop.RunEvent.metadata:type_name -> iop.RunEvent.MetadataEntry
26, // 6: iop.EdgeNodeEvent.metadata:type_name -> iop.EdgeNodeEvent.MetadataEntry
26, // 5: iop.RunEvent.metadata:type_name -> iop.RunEvent.MetadataEntry
27, // 6: iop.EdgeNodeEvent.metadata:type_name -> iop.EdgeNodeEvent.MetadataEntry
1, // 7: iop.CancelRequest.action:type_name -> iop.CancelAction
2, // 8: iop.NodeCommandRequest.type:type_name -> iop.NodeCommandType
27, // 9: iop.NodeCommandRequest.metadata:type_name -> iop.NodeCommandRequest.MetadataEntry
28, // 9: iop.NodeCommandRequest.metadata:type_name -> iop.NodeCommandRequest.MetadataEntry
2, // 10: iop.NodeCommandResponse.type:type_name -> iop.NodeCommandType
11, // 11: iop.NodeCommandResponse.usage_status:type_name -> iop.AgentUsageStatus
28, // 12: iop.NodeCommandResponse.result:type_name -> iop.NodeCommandResponse.ResultEntry
29, // 13: iop.AgentUsageStatus.metadata:type_name -> iop.AgentUsageStatus.MetadataEntry
15, // 14: iop.RegisterResponse.config:type_name -> iop.NodeConfigPayload
16, // 15: iop.NodeConfigPayload.adapters:type_name -> iop.AdapterConfig
23, // 16: iop.NodeConfigPayload.runtime:type_name -> iop.NodeRuntimeConfig
31, // 17: iop.AdapterConfig.settings:type_name -> google.protobuf.Struct
18, // 18: iop.AdapterConfig.cli:type_name -> iop.CLIAdapterConfig
21, // 19: iop.AdapterConfig.ollama:type_name -> iop.OllamaAdapterConfig
22, // 20: iop.AdapterConfig.vllm:type_name -> iop.VllmAdapterConfig
17, // 21: iop.AdapterConfig.mock:type_name -> iop.MockAdapterConfig
30, // 22: iop.CLIAdapterConfig.profiles:type_name -> iop.CLIAdapterConfig.ProfilesEntry
20, // 23: iop.CLIProfileConfig.completion_marker:type_name -> iop.CLICompletionMarker
19, // 24: iop.CLIAdapterConfig.ProfilesEntry.value:type_name -> iop.CLIProfileConfig
25, // [25:25] is the sub-list for method output_type
25, // [25:25] is the sub-list for method input_type
25, // [25:25] is the sub-list for extension type_name
25, // [25:25] is the sub-list for extension extendee
0, // [0:25] is the sub-list for field type_name
12, // 11: iop.NodeCommandResponse.usage_status:type_name -> iop.AgentUsageStatus
29, // 12: iop.NodeCommandResponse.result:type_name -> iop.NodeCommandResponse.ResultEntry
11, // 13: iop.NodeCommandResponse.provider_snapshots:type_name -> iop.ProviderSnapshot
30, // 14: iop.AgentUsageStatus.metadata:type_name -> iop.AgentUsageStatus.MetadataEntry
16, // 15: iop.RegisterResponse.config:type_name -> iop.NodeConfigPayload
17, // 16: iop.NodeConfigPayload.adapters:type_name -> iop.AdapterConfig
24, // 17: iop.NodeConfigPayload.runtime:type_name -> iop.NodeRuntimeConfig
32, // 18: iop.AdapterConfig.settings:type_name -> google.protobuf.Struct
19, // 19: iop.AdapterConfig.cli:type_name -> iop.CLIAdapterConfig
22, // 20: iop.AdapterConfig.ollama:type_name -> iop.OllamaAdapterConfig
23, // 21: iop.AdapterConfig.vllm:type_name -> iop.VllmAdapterConfig
18, // 22: iop.AdapterConfig.mock:type_name -> iop.MockAdapterConfig
31, // 23: iop.CLIAdapterConfig.profiles:type_name -> iop.CLIAdapterConfig.ProfilesEntry
21, // 24: iop.CLIProfileConfig.completion_marker:type_name -> iop.CLICompletionMarker
20, // 25: iop.CLIAdapterConfig.ProfilesEntry.value:type_name -> iop.CLIProfileConfig
26, // [26:26] is the sub-list for method output_type
26, // [26:26] is the sub-list for method input_type
26, // [26:26] is the sub-list for extension type_name
26, // [26:26] is the sub-list for extension extendee
0, // [0:26] is the sub-list for field type_name
}
func init() { file_proto_iop_runtime_proto_init() }
@ -2054,7 +2147,7 @@ func file_proto_iop_runtime_proto_init() {
if File_proto_iop_runtime_proto != nil {
return
}
file_proto_iop_runtime_proto_msgTypes[13].OneofWrappers = []any{
file_proto_iop_runtime_proto_msgTypes[14].OneofWrappers = []any{
(*AdapterConfig_Cli)(nil),
(*AdapterConfig_Ollama)(nil),
(*AdapterConfig_Vllm)(nil),
@ -2066,7 +2159,7 @@ func file_proto_iop_runtime_proto_init() {
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: 28,
NumMessages: 29,
NumExtensions: 0,
NumServices: 0,
},

View file

@ -80,6 +80,7 @@ message EdgeNodeSnapshot {
string label = 3;
bool connected = 4;
NodeConfigPayload config = 5;
repeated ProviderSnapshot provider_snapshots = 6;
}
// EdgeStatusResponse returns the Edge-owned node snapshot list in reply to an

View file

@ -112,6 +112,15 @@ message NodeCommandResponse {
// (capabilities, session_list, transport_status). usage_status keeps its
// dedicated typed payload.
map<string, string> result = 8;
repeated ProviderSnapshot provider_snapshots = 9;
}
message ProviderSnapshot {
string adapter = 1;
string status = 2; // unknown|available|unavailable
int32 capacity = 3;
int32 in_flight = 4;
int32 queued = 5;
}
message AgentUsageStatus {