- opencode SSE 스트림 파서 어댑터 추가 (opencode_sse.go) - 블랙박스/내부 테스트 파일 추가 - node/edge 애플리케이션의 CLI 어댑터 개선 - 설정 파일 및 README 업데이트 - agent-task opencode_sse_stream 추가
160 lines
6.6 KiB
Text
160 lines
6.6 KiB
Text
<!-- task=opencode_sse_stream plan=1 tag=REVIEW_OPENCODE_SSE -->
|
|
|
|
# Review Fix Plan - OpenCode SSE API Contract
|
|
|
|
## 이 파일을 읽는 구현 에이전트에게
|
|
|
|
**반드시 마지막에 `CODE_REVIEW-cloud-G07.md`의 모든 섹션을 실제 구현 내용과 검증 출력으로 채워야 한다. 이 파일 작성 전에는 작업이 완료된 것이 아니다.** 아래 체크리스트를 끝까지 수행하고, 중간/최종 검증 명령의 실제 stdout/stderr를 review 파일에 붙인다. review 파일의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 archive 지시(`*.log` rename, `complete.log` 작성)는 구현 에이전트가 실행하지 않는다.
|
|
|
|
## 배경
|
|
|
|
1차 구현은 테스트를 통과했지만 OpenCode server OpenAPI 계약과 맞지 않는 부분이 확인됐다. `/session/{sessionID}/prompt_async`의 `model` payload는 객체여야 하고, `session.status.status`는 문자열이 아니라 `SessionStatus` 객체다. 이 두 문제는 실서버에서 prompt 전송 실패나 stream completion 누락으로 이어질 수 있다.
|
|
|
|
## 분석 결과
|
|
|
|
### 읽은 파일
|
|
|
|
- `agent-task/opencode_sse_stream/plan_cloud_G07_0.log`
|
|
- `agent-task/opencode_sse_stream/code_review_cloud_G07_0.log`
|
|
- `apps/node/internal/adapters/cli/opencode_sse.go`
|
|
- `apps/node/internal/adapters/cli/opencode_sse_blackbox_test.go`
|
|
- `apps/node/internal/adapters/cli/opencode_sse_internal_test.go`
|
|
- `configs/edge.yaml`
|
|
- local OpenCode `1.14.46` `/doc` OpenAPI response
|
|
|
|
### 테스트 커버리지 공백
|
|
|
|
- `--model provider/model`이 `/prompt_async` body에서 `{providerID, modelID}` 객체로 전송되는지 검증하지 않는다.
|
|
- `session.status` 이벤트가 `{"status":{"type":"idle"}}` 형태로 올 때 complete 되는지 검증하지 않는다.
|
|
|
|
### 심볼 참조
|
|
|
|
- renamed/removed symbols: none.
|
|
- 새 helper 후보: `opencodeModelPayload`, `opencodeIdleStatus`.
|
|
|
|
### 범위 결정 근거
|
|
|
|
- `runtime.Adapter`, protobuf, Edge transport는 변경하지 않는다. 문제는 opencode SSE adapter 내부 HTTP payload/event parsing 계약에 한정된다.
|
|
- `configs/edge.yaml`의 opencode profile 이름과 args는 유지한다.
|
|
- 실제 opencode binary/model 통합 테스트는 제외한다. `httptest.Server`와 local OpenAPI 계약에 맞춘 unit/blackbox tests로 검증한다.
|
|
|
|
### 빌드 등급
|
|
|
|
- build=`cloud-G07`, review=`cloud-G07`: HTTP API 계약과 streaming completion 조건을 수정하므로 기존 등급을 유지한다.
|
|
|
|
### [REVIEW_OPENCODE_SSE-1] prompt_async model payload를 OpenCode schema에 맞춘다
|
|
|
|
#### 문제
|
|
|
|
```go
|
|
// apps/node/internal/adapters/cli/opencode_sse.go:562
|
|
if opts.Model != "" {
|
|
payload["model"] = opts.Model
|
|
}
|
|
```
|
|
|
|
OpenCode `/session/{sessionID}/prompt_async` schema는 `model`을 `{providerID, modelID}` 객체로 요구한다. 현재 구현은 문자열을 보내므로 `--model ollama-dgx/qwen3.6:35b-a3b-bf16` 사용 시 실서버가 Bad Request를 반환할 수 있다.
|
|
|
|
#### 해결 방법
|
|
|
|
`--model provider/model`을 첫 `/` 기준으로 분리한다. provider 또는 model이 비어 있거나 `/`가 없으면 명확한 error를 반환하고 `RuntimeEvent error`를 emit한다.
|
|
|
|
```go
|
|
// after
|
|
if opts.Model != "" {
|
|
model, err := opencodeModelPayload(opts.Model)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
payload["model"] = model
|
|
}
|
|
```
|
|
|
|
#### 수정 파일 및 체크리스트
|
|
|
|
- [ ] `apps/node/internal/adapters/cli/opencode_sse.go`: `opencodeModelPayload` helper 추가, `opencodePromptAsync`에서 객체 payload 사용.
|
|
- [ ] `apps/node/internal/adapters/cli/opencode_sse_internal_test.go`: 정상 split, `/` 없는 model, provider 빈 값, model 빈 값 테스트.
|
|
- [ ] `apps/node/internal/adapters/cli/opencode_sse_blackbox_test.go`: fake server의 `lastPromptBody`를 JSON decode해서 `model.providerID`, `model.modelID`, `parts[0].text`를 검증.
|
|
|
|
#### 테스트 작성
|
|
|
|
- 작성한다.
|
|
- `TestOpencodeModelPayload` 또는 동등한 이름으로 model parsing boundary를 검증한다.
|
|
- `TestCLIExecuteOpencodeSSE_StreamsTextDeltas`에 prompt body assertion을 추가한다.
|
|
|
|
#### 중간 검증
|
|
|
|
```bash
|
|
go test -count=1 ./apps/node/internal/adapters/cli
|
|
```
|
|
|
|
예상 결과: package pass. Go test cache는 허용하지 않는다.
|
|
|
|
### [REVIEW_OPENCODE_SSE-2] session.status 객체 idle을 complete로 처리한다
|
|
|
|
#### 문제
|
|
|
|
```go
|
|
// apps/node/internal/adapters/cli/opencode_sse.go:401
|
|
case "session.status":
|
|
if status, ok := stringFromProp(e.ev.Properties, "status"); ok && status == "idle" {
|
|
```
|
|
|
|
OpenCode `EventSessionStatus.properties.status`는 `SessionStatus` 객체다. `{"status":{"type":"idle"}}` 이벤트가 오면 현재 구현은 complete를 emit하지 않는다.
|
|
|
|
#### 해결 방법
|
|
|
|
문자열 `"idle"`과 객체 `{"type":"idle"}`를 모두 처리하는 helper를 추가한다.
|
|
|
|
```go
|
|
// after
|
|
case "session.status":
|
|
if opencodeStatusIdle(e.ev.Properties) {
|
|
return emitOpencodeComplete(ctx, spec.RunID, inputTokens, outputTokens, sink)
|
|
}
|
|
```
|
|
|
|
complete event 중복 코드는 작게 helper로 묶어도 된다. 단, 이 refactor는 `opencode_sse.go` 안에 한정한다.
|
|
|
|
#### 수정 파일 및 체크리스트
|
|
|
|
- [ ] `apps/node/internal/adapters/cli/opencode_sse.go`: `opencodeStatusIdle` helper 추가, `session.status` branch 수정.
|
|
- [ ] `apps/node/internal/adapters/cli/opencode_sse_internal_test.go`: string idle, object idle, busy/retry/non-map false 테스트.
|
|
- [ ] `apps/node/internal/adapters/cli/opencode_sse_blackbox_test.go`: `session.idle` 없이 `session.status` object idle만 보내도 run이 complete 되는 regression test 추가.
|
|
|
|
#### 테스트 작성
|
|
|
|
- 작성한다.
|
|
- `TestCLIExecuteOpencodeSSE_SessionStatusObjectIdleCompletes`를 추가한다.
|
|
|
|
#### 중간 검증
|
|
|
|
```bash
|
|
go test -count=1 ./apps/node/internal/adapters/cli
|
|
```
|
|
|
|
예상 결과: package pass. Go test cache는 허용하지 않는다.
|
|
|
|
## 수정 파일 요약
|
|
|
|
| 파일 | 항목 |
|
|
|------|------|
|
|
| `apps/node/internal/adapters/cli/opencode_sse.go` | REVIEW_OPENCODE_SSE-1, REVIEW_OPENCODE_SSE-2 |
|
|
| `apps/node/internal/adapters/cli/opencode_sse_internal_test.go` | REVIEW_OPENCODE_SSE-1, REVIEW_OPENCODE_SSE-2 |
|
|
| `apps/node/internal/adapters/cli/opencode_sse_blackbox_test.go` | REVIEW_OPENCODE_SSE-1, REVIEW_OPENCODE_SSE-2 |
|
|
|
|
## 최종 검증
|
|
|
|
```bash
|
|
go test -count=1 ./apps/node/internal/adapters/cli ./apps/node/internal/adapters ./apps/edge/internal/node ./apps/edge/internal/transport ./packages/config
|
|
```
|
|
|
|
예상 결과: package pass. Go test cache는 허용하지 않는다.
|
|
|
|
```bash
|
|
go test -count=1 ./...
|
|
```
|
|
|
|
예상 결과: repository-wide pass. Go test cache는 허용하지 않는다.
|
|
|
|
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 전체 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|