docs: add seulgivibe provider planning

This commit is contained in:
toki 2026-07-09 13:47:20 +09:00
parent 0d9f42970e
commit 514f9509fc
9 changed files with 1518 additions and 0 deletions

View file

@ -20,6 +20,10 @@ IOP의 OpenAI-compatible, A2A, IOP native 입력 표면에서 들어온 요청
- 경로: [openai-compatible-raw-tunnel-sideband-passthrough](../../archive/phase/routing-policy-model-orchestration/milestones/openai-compatible-raw-tunnel-sideband-passthrough.md)
- 요약: OpenAI-compatible provider 응답을 기존 Edge-Node proto-socket 위 lossless raw tunnel로 전달하고, 기본값은 provider-original `passthrough`로 두며, 요청이 명시한 경우에만 `passthrough+sideband` 또는 `transformed`를 사용한다.
- [계획] Seulgivibe OpenAI-compatible Provider 연동
- 경로: [seulgivibe-openai-compatible-provider](milestones/seulgivibe-openai-compatible-provider.md)
- 요약: Seulgivibe Claude/OpenAI 프록시를 OpenAI-compatible provider family로 관리하고, 정적 catalog, 요청 시점 provider token forwarding, Codex Responses passthrough를 구현한다.
- [스케치] OpenAI-compatible 하이브리드 라우팅과 컨텍스트 최적화
- 경로: [openai-compatible-hybrid-routing-context-optimization](milestones/openai-compatible-hybrid-routing-context-optimization.md)
- 요약: skill/예약어 기반 lane/grade 라우팅을 고신뢰 경로로 유지하고, DiffusionGemma 같은 로컬 모델을 자동 triage, negative guard, cloud context 최적화 보조, 주기적 scoring policy 학습 루프로 선택적으로 사용하는 hybrid local/cloud routing 방향을 스케치한다.

View file

@ -0,0 +1,82 @@
# Milestone: Seulgivibe OpenAI-compatible Provider 연동
## 위치
- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
- Phase: [PHASE.md](../PHASE.md)
## 목표
Seulgivibe의 Claude/OpenAI 프록시 경로를 IOP의 OpenAI-compatible provider family로 관리한다.
Claude 모델 3종과 OpenAI/Codex 모델 축을 정적 catalog로 노출하고, 사용자별 raw token은 IOP 호출 시점에 provider tunnel header로만 전달한다.
Codex `wire_api=responses` 경로가 provider tunnel을 통해 동작하도록 `/v1/responses` raw passthrough parity를 확보한다.
## 상태
[계획]
## 승격 조건
- 없음
## 구현 잠금
- 상태: 해제
- SDD: 필요
- SDD 문서: [SDD.md](../../../sdd/routing-policy-model-orchestration/seulgivibe-openai-compatible-provider/SDD.md)
- SDD 사유: OpenAI-compatible API/config schema, provider auth header, 외부 provider passthrough 계약이 바뀌는 Milestone이다.
- 잠금 해제 조건:
- [x] SDD 잠금이 해제되어 있다
- [x] SDD 사용자 리뷰가 없거나 승인/해결되었다
- [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다
- [x] Evidence Map이 완료 시 `Roadmap Completion`과 최종 검증 evidence로 검증 가능하게 연결되어 있다
- 결정 필요: 없음
## 범위
- Seulgivibe Claude/OpenAI proxy endpoint를 별도 provider id/type 축으로 관리하는 config/catalog 계약
- `seulgivibe_claude`, `seulgivibe_openai` provider type alias와 OpenAI-compatible adapter 재사용
- Claude 모델 `claude-sonnet-4-5`, `claude-opus-4-8`, `claude-fable-5` 정적 model catalog
- OpenAI/Codex 모델 `gpt-5.1`, `gpt-5.5` 정적 model catalog
- 사용자별 raw token을 inbound request header에서 읽어 provider tunnel `Authorization` header로 전달하는 경계
- Chat Completions provider tunnel과 Responses provider tunnel의 Seulgivibe passthrough 지원
## 기능
### Epic: [seulgivibe-provider] Seulgivibe Provider Surface
Seulgivibe를 IOP 내부에서는 provider-first OpenAI-compatible resource로 다루고, 외부 호출자는 OpenAI-compatible model id와 request-time provider token만 사용하게 하는 capability를 묶는다.
- [ ] [config-auth-catalog] Seulgivibe Claude/OpenAI provider aliases, `openai.provider_auth` schema, 정적 model catalog/계약 예시가 추가되어 있다. 검증: `go test ./packages/go/config -count=1`, `go test ./apps/edge/internal/node -count=1`, secret pattern scan이 통과한다.
- [ ] [provider-token-tunnel] Edge OpenAI Chat Completions provider tunnel이 configured request header의 raw user token을 provider `Authorization` header로 전달하고 missing-required를 dispatch 전에 차단한다. 검증: `go test ./apps/edge/internal/openai -count=1`이 auth forwarding/missing tests를 포함해 통과한다.
- [ ] [responses-passthrough] OpenAI-compatible provider route에서 `/v1/responses` raw passthrough가 동작하고 Codex-style unknown fields, streaming, provider auth, model rewrite를 보존/검증한다. 검증: `go test ./apps/edge/internal/openai -count=1`이 Responses passthrough tests를 포함해 통과한다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 기능 Task가 아직 충족되지 않았다.
- 검토 항목:
- [ ] 세 subtask의 `complete.log`가 각 Roadmap Completion task id를 기록한다.
- [ ] 최종 검증 출력이 SDD Evidence Map과 일치한다.
- [ ] 실제 token 값이 tracked 문서/config/test output에 남지 않았다.
- agent-ui 상태 반영: 해당 없음
- 리뷰 코멘트: 없음
## 범위 제외
- host-local `~/.claude/anthropic_key.sh`, `~/.codex/config.toml`, Pi coding 설정 변경
- 실제 JWT/API key/token 값을 tracked config, docs, task artifact에 저장
- Seulgivibe `/v1/models` endpoint를 catalog source of truth로 사용하는 방식
- provider response payload의 model echo rewrite 또는 sideband injection을 Responses passthrough에 강제하는 작업
- billing/chargeback, 조직 IAM, 장기 retention 정책
## 작업 컨텍스트
- 관련 경로: `packages/go/config`, `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/edge/internal/node`, `apps/node/internal/adapters/openai_compat`, `apps/node/internal/runtime`, `proto/iop/runtime.proto`, [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md)
- 표준선(선택): Seulgivibe는 새 wire adapter가 아니라 OpenAI-compatible provider family로 관리하고, provider별 특수 처리는 generation passthrough 밖의 auth/catalog/config 경계에만 둔다.
- 표준선(선택): 사용자별 provider token은 request-time raw value로만 받고, Edge가 provider tunnel request header로 변환한다. Node나 host-local helper script가 사용자 token source of truth가 되지 않는다.
- 표준선(선택): provider `/models` endpoint가 실패해도 IOP `/v1/models`는 top-level `models[]` catalog를 source of truth로 노출한다.
- 선행 작업: [OpenAI-compatible Raw Tunnel과 Sideband Passthrough](../../../archive/phase/routing-policy-model-orchestration/milestones/openai-compatible-raw-tunnel-sideband-passthrough.md), [Model Alias Provider Pool과 Provider Catalog](../../operational-observability-provider-management/milestones/provider-catalog-device-status.md)
- 후속 작업: 자동 route scorer 구현, provider auth per-provider granularity, Seulgivibe live smoke profile 정리
- 확인 필요: 없음

View file

@ -0,0 +1,111 @@
# SDD: Seulgivibe OpenAI-compatible Provider 연동
## 위치
- Milestone: [Seulgivibe OpenAI-compatible Provider 연동](../../../phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)
- Phase: [PHASE.md](../../../phase/routing-policy-model-orchestration/PHASE.md)
## 상태
[승인됨]
## SDD 잠금
- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목:
- 없음
## 문제 / 비목표
- 문제: Seulgivibe의 Claude/OpenAI 프록시 경로는 OpenAI-compatible 요청 형식을 사용하지만, IOP에는 이를 별도 provider family로 표현하고 사용자별 raw token을 요청 시점에만 provider로 전달하는 계약이 없다. Codex `wire_api=responses` 경로도 provider-pool Responses passthrough가 막혀 있어 config만으로 동작하지 않는다.
- 비목표:
- host-local Claude/Codex/Pi 설정 변경
- 실제 JWT/API key/token 저장
- Seulgivibe `/v1/models` endpoint를 runtime catalog source of truth로 사용하는 방식
- billing, 조직 IAM, 장기 token retention 정책
## Source of Truth
| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [Seulgivibe OpenAI-compatible Provider 연동](../../../phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md) | 목표, 기능 Task, 범위 제외 기준 |
| Contract | [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md) | 외부 OpenAI-compatible request/response, model list, provider auth header 계약 |
| Code | `packages/go/config`, `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/edge/internal/node`, `apps/node/internal/adapters/openai_compat`, `apps/node/internal/runtime`, `proto/iop/runtime.proto` | config schema, route dispatch, provider tunnel, Node adapter header relay 구현 기준 |
| External Provider | Seulgivibe Claude/OpenAI proxy | Claude path와 OpenAI path 모두 OpenAI-compatible provider로 취급한다. Catalog는 IOP static config가 source of truth다. |
| User Decision | 없음 | 세부 구현은 기존 provider-first/openai_compat/passthrough 표준선으로 확정 가능하다. |
## State Machine
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| `provider-configured` | Edge config가 `seulgivibe_claude` 또는 `seulgivibe_openai` provider와 `models[]` catalog를 가진다 | `request-received` | config load/validation |
| `request-received` | OpenAI-compatible Chat 또는 Responses request가 catalog model id를 사용한다 | `provider-auth-resolved` 또는 `provider-auth-missing` | Edge OpenAI handler |
| `provider-auth-resolved` | `openai.provider_auth.enabled=true`이고 configured request header에 raw token이 있다 | `provider-tunnel-dispatched` | request header extraction |
| `provider-auth-missing` | provider auth가 required인데 request header가 없다 | terminal error | 400 invalid_request_error, no dispatch |
| `provider-tunnel-dispatched` | Edge가 provider-pool target을 선택하고 provider tunnel request를 Node에 보낸다 | `provider-response-relayed` 또는 `provider-error` | `SubmitProviderTunnelRequest` |
| `provider-response-relayed` | Node openai_compat adapter가 provider HTTP response frames를 보낸다 | terminal success | raw tunnel frames |
| `provider-error` | provider HTTP request 또는 tunnel relay가 실패한다 | terminal error | provider tunnel error frame |
## Interface Contract
- 계약 원문: [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md)
- 입력:
- `nodes[].providers[].type`: `seulgivibe_claude` 또는 `seulgivibe_openai`를 허용하고 runtime type은 `openai_compat`로 정규화한다.
- `nodes[].providers[].endpoint`: Seulgivibe Claude path는 `/anthropic/v1`, OpenAI path는 `/openai/v1` compatible base URL이다.
- `nodes[].providers[].models`: provider가 serve 가능한 concrete model id 목록이다.
- `models[].id`: caller-facing model id이며 IOP `/v1/models`의 source of truth다.
- `models[].providers`: provider id에서 concrete served model id로 가는 mapping이다.
- `openai.provider_auth.from_header`: caller가 raw user token을 넣는 request header다.
- `openai.provider_auth.target_header`: provider request에 전달할 header다. 기본값은 `Authorization`이다.
- `openai.provider_auth.scheme`: raw token 앞에 붙일 scheme이다. 기본값은 `Bearer`다.
- 출력:
- IOP `/v1/models`: static `models[]` catalog의 Seulgivibe model ids를 반환한다.
- Chat Completions provider tunnel: provider status/header/body bytes를 raw relay하고 provider auth header를 포함한다.
- Responses provider tunnel: `/v1/responses` raw body를 model rewrite 외에는 보존해 provider로 전달하고 raw response를 relay한다.
- 금지:
- IOP runtime이 `~/.claude/anthropic_key.sh`, `~/.codex/config.toml`, local env helper를 token source로 읽지 않는다.
- 실제 token 값을 tracked config/docs/task artifact/log에 쓰지 않는다.
- inbound IOP auth `Authorization` header를 provider token source로 재사용하지 않는다.
- Seulgivibe `/v1/models` 실패를 IOP catalog 노출 실패로 연결하지 않는다.
- Responses passthrough body를 Chat Completions shape인 `max_tokens`로 변환하지 않는다.
## Acceptance Scenarios
| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `config-auth-catalog` | Seulgivibe Claude/OpenAI provider-first config가 있다 | Edge config load와 node mapper compile을 실행한다 | alias type이 `openai_compat` runtime으로 정규화되고 provider label은 Seulgivibe family를 보존한다 |
| S02 | `config-auth-catalog` | `/v1/models` provider endpoint가 catalog source로 안정적이지 않다 | IOP model list와 provider pool validation을 수행한다 | static `models[]` catalog가 Claude/OpenAI model ids를 노출하고 provider served model membership을 검증한다 |
| S03 | `provider-token-tunnel` | caller가 configured provider auth header에 raw user token을 넣는다 | Chat Completions provider tunnel을 연다 | Edge가 provider `Authorization` header를 만들고 missing-required는 dispatch 전에 400으로 차단한다 |
| S04 | `responses-passthrough` | Codex-style `/v1/responses` payload가 provider-pool model id, unknown fields, `stream`을 포함할 수 있다 | Responses provider route가 실행된다 | Edge가 strict normalized parser를 우회해 raw body를 model rewrite만 적용한 뒤 `/v1/responses` provider tunnel로 전달한다 |
## Evidence Map
| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | config and mapper unit tests | `agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog` | `Roadmap Completion``config-auth-catalog``go test ./packages/go/config -count=1`, `go test ./apps/edge/internal/node -count=1` 결과 |
| S02 | static catalog validation and secret scan | `agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog` | `Roadmap Completion``config-auth-catalog`와 secret pattern scan 결과 |
| S03 | Edge OpenAI handler auth forwarding/missing tests | `agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth` | `Roadmap Completion``provider-token-tunnel``go test ./apps/edge/internal/openai -count=1` 결과 |
| S04 | Edge OpenAI Responses tunnel tests | `agent-task/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough` | `Roadmap Completion``responses-passthrough``go test ./apps/edge/internal/openai -count=1` 결과 |
## Cross-repo Dependencies
- 없음
## Drift Check
- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.
## 사용자 리뷰 이력
- 없음
## 작업 컨텍스트
- 표준선: Seulgivibe는 OpenAI-compatible provider family로 관리하고, generation request body는 provider tunnel passthrough를 우선한다.
- 표준선: 사용자별 provider token은 request-time header value이며 IOP config에는 token source와 forwarding rule만 둔다.
- 표준선: `/v1/responses` provider route는 provider-original passthrough를 우선하고, response body model echo rewrite나 sideband insertion은 하지 않는다.
- 후속 SDD: 없음

View file

@ -0,0 +1,152 @@
<!-- task=m-seulgivibe-openai-compatible-provider/01_config_auth_catalog plan=0 tag=SEULGI_CONFIG -->
# Code Review Reference - SEULGI_CONFIG
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## 개요
date=2026-07-09
task=m-seulgivibe-openai-compatible-provider/01_config_auth_catalog, plan=0, tag=SEULGI_CONFIG
## Roadmap Targets
- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)
- Task ids:
- `config-auth-catalog`: Seulgivibe Claude/OpenAI provider aliases, `openai.provider_auth` schema, 정적 model catalog/계약 예시
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G06.md` -> `code_review_cloud_G06_N.log`, `PLAN-cloud-G06.md` -> `plan_cloud_G06_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. 이 task는 milestone-linked task라 Roadmap Completion을 확인한다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [SEULGI_CONFIG-1] Provider type alias | [ ] |
| [SEULGI_CONFIG-2] Provider auth config schema | [ ] |
| [SEULGI_CONFIG-3] Static catalog and contract notes | [ ] |
## 구현 체크리스트
- [ ] [SEULGI_CONFIG-1] `seulgivibe_claude`, `seulgivibe_openai` provider type을 `openai_compat` runtime으로 정규화하고 mapper provider label에 보존한다.
- [ ] [SEULGI_CONFIG-2] `openai.provider_auth` config schema/default/validation을 추가하되 실제 token 값이나 local helper path를 참조하지 않는다.
- [ ] [SEULGI_CONFIG-3] Seulgivibe 정적 model catalog/계약 예시를 추가하고 실제 endpoint secret/token은 문서에 쓰지 않는다.
- [ ] `go test ./packages/go/config -count=1``go test ./apps/edge/internal/node -count=1`를 실행한다.
- [ ] 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-seulgivibe-openai-compatible-provider/01_config_auth_catalog/``agent-task/archive/YYYY/MM/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-seulgivibe-openai-compatible-provider/`를 제거하거나, 남은 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가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- Seulgivibe aliases are normalized only to the runtime adapter type and still preserve provider labels when `provider:` is omitted.
- `openai.provider_auth` is disabled by default and never stores or reads actual token values.
- Contracts/examples use placeholder-only endpoint/token material and document request-time token injection.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
### SEULGI_CONFIG-1 중간 검증
```
$ go test ./packages/go/config -run 'TestLoadEdge_SeulgivibeProviderFirstAliases' -count=1
(output)
$ go test ./apps/edge/internal/node -run 'TestBuildConfigPayload_ProviderFirstSeulgivibeDefaultsProviderFromType' -count=1
(output)
```
### SEULGI_CONFIG-2 중간 검증
```
$ go test ./packages/go/config -run 'TestLoadEdge_OpenAIProviderAuth|TestLoadEdge_OpenAIDefaults|TestLoadEdge_OpenAIOverride' -count=1
(output)
```
### SEULGI_CONFIG-3 중간 검증
```
$ rg --sort path -n 'e[y]J|secret -|OPENAI_API_KEY=.*e[y]J' agent-contract configs
(output)
$ test $? -eq 1
(output)
```
### 최종 검증
```
$ go test ./packages/go/config -count=1
(output)
$ go test ./apps/edge/internal/node -count=1
(output)
$ rg --sort path -n 'e[y]J|secret -|OPENAI_API_KEY=.*e[y]J' agent-contract configs
(output)
$ test $? -eq 1
(output)
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

View file

@ -0,0 +1,335 @@
<!-- task=m-seulgivibe-openai-compatible-provider/01_config_auth_catalog plan=0 tag=SEULGI_CONFIG -->
# Implementation Plan - SEULGI_CONFIG
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수 완료 조건이다. 구현 후 검증을 실행하고, 실제 stdout/stderr를 붙이고, active 파일은 그대로 둔 뒤 review ready로 보고한다. selected Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막는 경우에만 review stub의 `사용자 리뷰 요청`에 정확한 근거를 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 외부 secret/service 준비, 검증 증거 공백, 일반 범위 조정은 사용자 리뷰 요청이 아니라 후속 plan 또는 검증 기록으로 처리한다. finalization은 code-review-skill 전용이다.
## 배경
Seulgivibe Claude/OpenAI 경로는 OpenAI-compatible 요청 형식으로 동작하지만, IOP config에는 이를 별도 provider 축으로 관리할 명시적 타입과 사용자별 provider token 설정 지점이 없다. 이 plan은 구현 전 foundation만 만든다: provider type alias, Edge-level provider auth config schema, 정적 model catalog 예시/계약을 추가한다. 실제 요청 헤더 주입과 `/v1/responses` passthrough는 후속 split task에서 처리한다.
## 사용자 리뷰 요청 흐름
선택된 Milestone lock decision이 실구현을 차단할 때만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 직접 사용자에게 질문하지 않고, code-review가 그 요청을 검증해 `USER_REVIEW.md` 작성 여부를 결정한다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)
- Task ids:
- `config-auth-catalog`: Seulgivibe Claude/OpenAI provider aliases, `openai.provider_auth` schema, 정적 model catalog/계약 예시
- Completion mode: check-on-pass
## 분석 결과
### 읽은 파일
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/node-smoke.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-contract/index.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-spec/index.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`
- `agent-spec/input/openai-compatible-surface.md`
- `packages/go/config/config.go`
- `packages/go/config/config_test.go`
- `apps/edge/internal/node/mapper.go`
- `apps/edge/internal/node/mapper_test.go`
- `proto/iop/runtime.proto`
- `apps/node/internal/runtime/types.go`
- `apps/node/internal/adapters/config_set.go`
- `apps/node/internal/adapters/openai_compat/openai_compat.go`
- `apps/node/internal/adapters/openai_compat/openai_compat_test.go`
- `apps/edge/internal/openai/routes.go`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/stream.go`
- `apps/edge/internal/openai/responses_handler.go`
- `apps/edge/internal/openai/types.go`
- `apps/edge/internal/openai/server_test.go`
- `apps/edge/internal/service/run_dispatch.go`
### SDD 기준
- SDD: [SDD.md](agent-roadmap/sdd/routing-policy-model-orchestration/seulgivibe-openai-compatible-provider/SDD.md)
- 상태: `[승인됨]`
- SDD 잠금: 해제
- 대상 Acceptance Scenario:
- S01 -> `config-auth-catalog`: Seulgivibe provider alias가 `openai_compat` runtime으로 정규화되고 provider label을 보존한다.
- S02 -> `config-auth-catalog`: static `models[]` catalog가 Seulgivibe model ids를 노출하고 served model membership을 검증한다.
- Evidence Map 반영: 이 plan의 config/mapper tests와 secret scan이 `config-auth-catalog` Roadmap Completion evidence가 된다.
### 테스트 환경 규칙
- `test_env=local`.
- `agent-test/local/rules.md`를 읽었다.
- 매칭 profile: `agent-test/local/platform-common-smoke.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/node-smoke.md`.
- 이 subtask는 `packages/go/config/**`, `apps/edge/internal/node/**`, 계약 문서만 변경한다. 적용 명령은 `go test ./packages/go/config -count=1``go test ./apps/edge/internal/node -count=1`이다.
- local checkout 밖으로 나가는 필수 검증은 없다. live Seulgivibe 호출은 secret과 사내망에 의존하므로 이 subtask의 필수 검증이 아니다.
### 테스트 커버리지 공백
- `seulgivibe_claude`, `seulgivibe_openai` provider type alias: 기존 테스트 없음. 새 config/mapper 테스트 필요.
- `openai.provider_auth` schema/default/validation: 기존 테스트 없음. 새 config 테스트 필요.
- 정적 model catalog로 Seulgivibe 모델을 노출하는 예시/계약: 기존 문서 없음. 계약 또는 예시 업데이트 필요.
### 심볼 참조
renamed/removed symbol 없음. 새 symbol 후보: `EdgeOpenAIProviderAuthConf`, `ProviderAuth`.
### 분할 판단
split decision policy를 먼저 평가했다. shared task group은 `m-seulgivibe-openai-compatible-provider`다.
- `01_config_auth_catalog`: independent. config/schema/catalog foundation.
- `02+01_provider_tunnel_auth`: depends on 01. Edge request header를 provider tunnel header로 주입.
- `03+01,02_responses_passthrough`: depends on 01 and 02. `/v1/responses` raw passthrough parity.
이 plan은 01만 수행한다. 후속 predecessor는 아직 missing이며, 02/03 구현은 01 PASS 후 시작해야 한다.
### 범위 결정 근거
- `~/.claude/anthropic_key.sh`, `~/.codex/config.toml`, host-local Claude/Codex/Pi 설정은 수정하지 않는다.
- 실제 token 값을 tracked file, log, test fixture에 쓰지 않는다.
- Edge handler의 provider auth 주입은 02에서 처리한다.
- `/v1/responses` provider tunnel 지원은 03에서 처리한다.
- Node adapter/proto 변경은 이 subtask 범위가 아니다. 현재 `ProviderTunnelRequest.Headers`가 존재하므로 foundation 단계에서 proto 확장은 필요 없다.
### 빌드 등급
`cloud-G06`. config 계약, provider routing label, secret-handling schema가 맞물리고 후속 Edge/API behavior를 결정하므로 local 단독 판단보다 cross-domain context가 필요하다.
## 구현 체크리스트
- [ ] [SEULGI_CONFIG-1] `seulgivibe_claude`, `seulgivibe_openai` provider type을 `openai_compat` runtime으로 정규화하고 mapper provider label에 보존한다.
- [ ] [SEULGI_CONFIG-2] `openai.provider_auth` config schema/default/validation을 추가하되 실제 token 값이나 local helper path를 참조하지 않는다.
- [ ] [SEULGI_CONFIG-3] Seulgivibe 정적 model catalog/계약 예시를 추가하고 실제 endpoint secret/token은 문서에 쓰지 않는다.
- [ ] `go test ./packages/go/config -count=1``go test ./apps/edge/internal/node -count=1`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [SEULGI_CONFIG-1] Provider Type Alias
#### 문제
`packages/go/config/config.go:26-37``NormalizeProviderType``openai_api`, `vllm`, `lemonade`, `sglang``openai_compat`로 정규화한다. `apps/edge/internal/node/mapper.go:239-245`의 provider label 기본값도 Seulgivibe 계열 type을 보존하지 않는다.
Before:
```go
// packages/go/config/config.go:28
switch strings.ToLower(strings.TrimSpace(t)) {
case "openai_compat", "openai_api", "vllm", "lemonade", "sglang":
return "openai_compat"
```
```go
// apps/edge/internal/node/mapper.go:243
switch strings.TrimSpace(p.Type) {
case "vllm", "lemonade", "sglang", "openai_api":
return strings.TrimSpace(p.Type)
```
#### 해결 방법
`seulgivibe_claude`, `seulgivibe_openai`는 runtime adapter type은 `openai_compat`로 정규화하되, provider label은 명시 `provider:`가 없을 때 원래 type을 유지한다.
After:
```go
// packages/go/config/config.go:28
switch strings.ToLower(strings.TrimSpace(t)) {
case "openai_compat", "openai_api", "vllm", "lemonade", "sglang", "seulgivibe_claude", "seulgivibe_openai":
return "openai_compat"
```
```go
// apps/edge/internal/node/mapper.go:243
switch strings.TrimSpace(p.Type) {
case "vllm", "lemonade", "sglang", "openai_api", "seulgivibe_claude", "seulgivibe_openai":
return strings.TrimSpace(p.Type)
```
#### 수정 파일 및 체크리스트
- [ ] `packages/go/config/config.go`: alias 추가.
- [ ] `apps/edge/internal/node/mapper.go`: provider label default 추가.
- [ ] `packages/go/config/config_test.go`: provider-first Seulgivibe alias가 load/validate되는 테스트 추가.
- [ ] `apps/edge/internal/node/mapper_test.go`: alias provider가 `OpenAICompatAdapterConfig.Provider`로 보존되는 테스트 추가.
#### 테스트 작성
- `TestLoadEdge_SeulgivibeProviderFirstAliases`: `nodes[].providers[].type` alias 두 개와 `models[]` membership validation을 검증.
- `TestBuildConfigPayload_ProviderFirstSeulgivibeDefaultsProviderFromType`: proto config의 `Provider`가 alias 문자열로 들어가는지 검증.
#### 중간 검증
```bash
go test ./packages/go/config -run 'TestLoadEdge_SeulgivibeProviderFirstAliases' -count=1
go test ./apps/edge/internal/node -run 'TestBuildConfigPayload_ProviderFirstSeulgivibeDefaultsProviderFromType' -count=1
```
### [SEULGI_CONFIG-2] Provider Auth Config Schema
#### 문제
`packages/go/config/config.go:376-389``EdgeOpenAIConf`에는 inbound IOP auth인 `bearer_token`만 있고, 사용자별 provider token을 별도 header에서 받아 tunnel 호출에 전달한다는 설정이 없다. `apps/edge/internal/openai/routes.go:20-29`는 inbound `Authorization`을 IOP auth로 이미 사용하므로 provider token을 같은 header로 재사용하면 충돌한다.
Before:
```go
// packages/go/config/config.go:376
type EdgeOpenAIConf struct {
Enabled bool `mapstructure:"enabled" yaml:"enabled"`
Listen string `mapstructure:"listen" yaml:"listen"`
BearerToken string `mapstructure:"bearer_token" yaml:"bearer_token"`
```
#### 해결 방법
Edge OpenAI surface에 optional provider auth config를 추가한다. 기본은 disabled라 기존 동작이 바뀌지 않는다. enabled일 때 구현 task 02가 `from_header`의 raw token을 읽어 `target_header`로 전달한다.
After:
```go
type EdgeOpenAIProviderAuthConf struct {
Enabled bool `mapstructure:"enabled" yaml:"enabled,omitempty"`
FromHeader string `mapstructure:"from_header" yaml:"from_header,omitempty"`
TargetHeader string `mapstructure:"target_header" yaml:"target_header,omitempty"`
Scheme string `mapstructure:"scheme" yaml:"scheme,omitempty"`
Required bool `mapstructure:"required" yaml:"required,omitempty"`
}
type EdgeOpenAIConf struct {
Enabled bool `mapstructure:"enabled" yaml:"enabled"`
Listen string `mapstructure:"listen" yaml:"listen"`
BearerToken string `mapstructure:"bearer_token" yaml:"bearer_token"`
ProviderAuth EdgeOpenAIProviderAuthConf `mapstructure:"provider_auth" yaml:"provider_auth,omitempty"`
```
Default/validation rule:
- disabled by default.
- if enabled and empty: `from_header=X-IOP-Provider-Authorization`, `target_header=Authorization`, `scheme=Bearer`, `required=true`.
- reject empty `from_header` or `target_header` after default normalization only when enabled.
- never load token value from local files, env files, or helper scripts in this schema.
#### 수정 파일 및 체크리스트
- [ ] `packages/go/config/config.go`: struct, default normalization, validation 추가.
- [ ] `packages/go/config/config_test.go`: defaults, override, invalid enabled config 테스트 추가.
- [ ] 기존 `TestLoadEdge_OpenAIDefaults``TestLoadEdge_OpenAIOverride`가 깨지지 않게 기대값 갱신.
#### 테스트 작성
- `TestLoadEdge_OpenAIProviderAuthDefaultsDisabled`: 기본 disabled 확인.
- `TestLoadEdge_OpenAIProviderAuthEnabledDefaults`: enabled일 때 default header/scheme/required 확인.
- `TestLoadEdge_OpenAIProviderAuthRejectsBlankHeaders`: enabled 상태에서 blank override reject 확인.
#### 중간 검증
```bash
go test ./packages/go/config -run 'TestLoadEdge_OpenAIProviderAuth|TestLoadEdge_OpenAIDefaults|TestLoadEdge_OpenAIOverride' -count=1
```
### [SEULGI_CONFIG-3] Static Catalog And Contract Notes
#### 문제
`packages/go/config/config.go:279-303``ModelCatalogEntry`는 정적 model catalog를 지원하지만 Seulgivibe 모델 3개와 Codex/OpenAI 모델 축을 설명하는 계약/예시가 없다. `/anthropic/v1/models``/openai/v1/models`가 안정적 catalog source가 아니라면 runtime은 config catalog를 source of truth로 삼아야 한다.
#### 해결 방법
계약/예시에 provider-first 구성을 추가하되 실제 secret/token은 쓰지 않는다. endpoint는 필요 시 placeholder 또는 사내 문서 포인터로 두고, token은 반드시 요청 시 `openai.provider_auth.from_header`로 들어온 raw user token을 task 02가 전달한다고 명시한다.
Example shape:
```yaml
openai:
provider_auth:
enabled: true
from_header: X-IOP-Provider-Authorization
target_header: Authorization
scheme: Bearer
required: true
nodes:
- providers:
- id: seulgivibe-claude
type: seulgivibe_claude
category: api
endpoint: https://<seulgivibe-host>/anthropic/v1
models: [claude-sonnet-4-5, claude-opus-4-8, claude-fable-5]
- id: seulgivibe-openai
type: seulgivibe_openai
category: api
endpoint: https://<seulgivibe-host>/openai/v1
models: [gpt-5.1, gpt-5.5]
models:
- id: claude-sonnet-4-5
providers: {seulgivibe-claude: claude-sonnet-4-5}
- id: claude-opus-4-8
providers: {seulgivibe-claude: claude-opus-4-8}
- id: claude-fable-5
providers: {seulgivibe-claude: claude-fable-5}
- id: gpt-5.1
providers: {seulgivibe-openai: gpt-5.1}
- id: gpt-5.5
providers: {seulgivibe-openai: gpt-5.5}
```
#### 수정 파일 및 체크리스트
- [ ] `agent-contract/inner/edge-config-runtime-refresh.md`: config schema/refresh impact를 기록.
- [ ] `agent-contract/outer/openai-compatible-api.md`: provider auth request header와 token non-persistence rule 기록.
- [ ] 필요 시 existing sample config에 placeholder-only example 추가. 실제 Seulgivibe token/secret은 금지.
#### 테스트 작성
문서/예시 변경은 unit test가 직접 커버하지 않는다. `SEULGI_CONFIG-1/2`의 config tests가 schema와 catalog validation을 검증한다.
#### 중간 검증
```bash
rg --sort path -n 'e[y]J|secret -|OPENAI_API_KEY=.*e[y]J' agent-contract configs
test $? -eq 1
```
Expected: `rg` prints no matches, then `test $? -eq 1` passes.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `packages/go/config/config.go` | SEULGI_CONFIG-1, SEULGI_CONFIG-2 |
| `packages/go/config/config_test.go` | SEULGI_CONFIG-1, SEULGI_CONFIG-2 |
| `apps/edge/internal/node/mapper.go` | SEULGI_CONFIG-1 |
| `apps/edge/internal/node/mapper_test.go` | SEULGI_CONFIG-1 |
| `agent-contract/inner/edge-config-runtime-refresh.md` | SEULGI_CONFIG-3 |
| `agent-contract/outer/openai-compatible-api.md` | SEULGI_CONFIG-3 |
| `configs/**` | SEULGI_CONFIG-3, only if an existing sample is the correct place |
## 최종 검증
```bash
go test ./packages/go/config -count=1
go test ./apps/edge/internal/node -count=1
rg --sort path -n 'e[y]J|secret -|OPENAI_API_KEY=.*e[y]J' agent-contract configs
test $? -eq 1
```
Expected: Go tests pass. `rg` prints no real token references and `test $? -eq 1` passes; placeholder strings are acceptable only when they are not token-shaped.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,135 @@
<!-- task=m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth plan=0 tag=SEULGI_TUNNEL -->
# Code Review Reference - SEULGI_TUNNEL
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## 개요
date=2026-07-09
task=m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth, plan=0, tag=SEULGI_TUNNEL
## Roadmap Targets
- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)
- Task ids:
- `provider-token-tunnel`: Edge OpenAI Chat Completions provider tunnel이 request-time raw user token을 provider Authorization header로 전달
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. 이 task는 milestone-linked task라 Roadmap Completion을 확인한다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [SEULGI_TUNNEL-1] Provider auth header helper | [ ] |
| [SEULGI_TUNNEL-2] Chat provider tunnel injection | [ ] |
## 구현 체크리스트
- [ ] [SEULGI_TUNNEL-1] Edge OpenAI server에 provider auth header 추출/포맷 helper를 추가한다.
- [ ] [SEULGI_TUNNEL-2] Chat Completions provider tunnel 요청에 provider auth headers를 주입하고 missing-required behavior를 테스트한다.
- [ ] `go test ./apps/edge/internal/openai -count=1`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_cloud_G07_N.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_cloud_G07_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-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/``agent-task/archive/YYYY/MM/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-seulgivibe-openai-compatible-provider/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md``CODE_REVIEW-cloud-G07.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가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- Implementation starts only after predecessor `01_config_auth_catalog` has `complete.log`.
- Provider token is read only from configured request header and never from local helper/env files.
- Missing required provider auth returns before `SubmitProviderTunnel`.
- Inbound IOP `Authorization` and outbound provider `Authorization` remain separate.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
### SEULGI_TUNNEL-1 중간 검증
```
$ go test ./apps/edge/internal/openai -run 'TestProviderTunnelAuthHeaders|TestChatProviderTunnel' -count=1
(output)
```
### SEULGI_TUNNEL-2 중간 검증
```
$ go test ./apps/edge/internal/openai -run 'TestChatProviderTunnelProviderAuth|TestChatProviderTunnelForwardsProviderAuthHeader' -count=1
(output)
```
### 최종 검증
```
$ go test ./apps/edge/internal/openai -count=1
(output)
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

View file

@ -0,0 +1,251 @@
<!-- task=m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth plan=0 tag=SEULGI_TUNNEL -->
# Implementation Plan - SEULGI_TUNNEL
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수 완료 조건이다. 구현 후 검증을 실행하고, 실제 stdout/stderr를 붙이고, active 파일은 그대로 둔 뒤 review ready로 보고한다. selected Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막는 경우에만 review stub의 `사용자 리뷰 요청`에 정확한 근거를 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 외부 secret/service 준비, 검증 증거 공백, 일반 범위 조정은 사용자 리뷰 요청이 아니라 후속 plan 또는 검증 기록으로 처리한다. finalization은 code-review-skill 전용이다.
## 배경
Seulgivibe provider token은 사용자마다 다르고 raw text로 전달되어야 하므로 host-local `~/.claude/anthropic_key.sh`나 env secret을 IOP runtime이 읽으면 안 된다. 현재 raw provider tunnel에는 per-request headers가 이미 있으며, Node openai_compat adapter는 tunnel header를 provider request에 적용한다. 이 plan은 Edge OpenAI surface에서 `openai.provider_auth` 설정에 따라 inbound header를 provider tunnel header로 변환하는 작업만 다룬다.
## 사용자 리뷰 요청 흐름
선택된 Milestone lock decision이 실구현을 차단할 때만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 직접 사용자에게 질문하지 않고, code-review가 그 요청을 검증해 `USER_REVIEW.md` 작성 여부를 결정한다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)
- Task ids:
- `provider-token-tunnel`: Edge OpenAI Chat Completions provider tunnel이 request-time raw user token을 provider Authorization header로 전달
- Completion mode: check-on-pass
## 분석 결과
### 읽은 파일
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-spec/input/openai-compatible-surface.md`
- `packages/go/config/config.go`
- `apps/edge/internal/openai/routes.go`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/stream.go`
- `apps/edge/internal/openai/server_test.go`
- `apps/edge/internal/service/run_dispatch.go`
- `proto/iop/runtime.proto`
- `apps/node/internal/runtime/types.go`
- `apps/node/internal/adapters/openai_compat/openai_compat.go`
- `apps/node/internal/adapters/openai_compat/openai_compat_test.go`
### SDD 기준
- SDD: [SDD.md](agent-roadmap/sdd/routing-policy-model-orchestration/seulgivibe-openai-compatible-provider/SDD.md)
- 상태: `[승인됨]`
- SDD 잠금: 해제
- 대상 Acceptance Scenario:
- S03 -> `provider-token-tunnel`: configured provider auth request header의 raw user token을 provider `Authorization` header로 전달하고 missing-required는 dispatch 전에 차단한다.
- Evidence Map 반영: 이 plan의 Edge OpenAI handler auth forwarding/missing tests가 `provider-token-tunnel` Roadmap Completion evidence가 된다.
### 테스트 환경 규칙
- `test_env=local`.
- `agent-test/local/rules.md``agent-test/local/edge-smoke.md`를 읽었다.
- 이 subtask는 Edge OpenAI handler/test 변경이다. 적용 명령은 `go test ./apps/edge/internal/openai -count=1`이다.
- Node/proto는 수정하지 않는다. 기존 Node tunnel header behavior는 `apps/node/internal/adapters/openai_compat/openai_compat_test.go`의 tunnel tests로 이미 존재하므로 이 subtask의 필수 명령에는 포함하지 않는다.
### 테스트 커버리지 공백
- Edge가 inbound provider auth header를 `SubmitProviderTunnelRequest.Headers.Authorization`으로 넣는 테스트 없음.
- provider auth required인데 header가 없을 때 provider tunnel을 열지 않는 테스트 없음.
- configured IOP inbound `Authorization`과 provider token header가 분리되는 테스트 없음.
### 심볼 참조
renamed/removed symbol 없음. 01에서 추가된 `EdgeOpenAIProviderAuthConf`/`ProviderAuth`를 읽는 helper를 새로 추가한다.
### 분할 판단
shared task group은 `m-seulgivibe-openai-compatible-provider`다.
- predecessor `01_config_auth_catalog`: 현재 active predecessor이며 `complete.log` 없음. 구현은 `agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/complete.log` 또는 matching archive complete가 생긴 뒤 시작해야 한다.
- 이 plan은 `02+01_provider_tunnel_auth`로 01에만 의존한다.
- `03+01,02_responses_passthrough`는 이 task 완료 후 시작한다.
### 범위 결정 근거
- `~/.claude/anthropic_key.sh`, local env, Codex config는 읽지 않는다.
- 실제 token 값을 config, metadata, log, test output에 남기지 않는다.
- proto/service/Node adapter 변경은 하지 않는다. `SubmitProviderTunnelRequest.Headers``ProviderTunnelRequest.headers`가 이미 있다.
- `/v1/responses` passthrough는 03에서 처리한다. 이 task는 Chat Completions provider tunnel auth에 집중한다.
### 빌드 등급
`cloud-G07`. secret boundary, inbound/outbound auth header separation, external provider compatibility가 핵심이라 handler-only 수정처럼 보여도 API behavior 판단이 중요하다.
## 의존 관계 및 구현 순서
이 plan의 디렉터리명 `02+01_provider_tunnel_auth`는 predecessor index `01`을 요구한다. 구현 시작 전 `agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/complete.log` 또는 matching archive complete가 있어야 한다. predecessor가 없으면 구현하지 말고 review stub에 verification blocker로 기록한다.
## 구현 체크리스트
- [ ] [SEULGI_TUNNEL-1] Edge OpenAI server에 provider auth header 추출/포맷 helper를 추가한다.
- [ ] [SEULGI_TUNNEL-2] Chat Completions provider tunnel 요청에 provider auth headers를 주입하고 missing-required behavior를 테스트한다.
- [ ] `go test ./apps/edge/internal/openai -count=1`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [SEULGI_TUNNEL-1] Provider Auth Header Helper
#### 문제
`apps/edge/internal/openai/routes.go:20-29`는 inbound `Authorization`을 IOP auth로 사용한다. provider token을 같은 header에서 읽으면 IOP auth와 충돌한다. 반면 `apps/edge/internal/service/run_dispatch.go:787-814``1023-1052`에는 tunnel request headers가 이미 있고, `apps/node/internal/runtime/types.go:217-231``proto/iop/runtime.proto:65-78`도 headers를 전달한다.
Before:
```go
// apps/edge/internal/openai/routes.go:22
if strings.TrimSpace(s.cfg.BearerToken) != "" {
expected := "Bearer " + s.cfg.BearerToken
if subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), []byte(expected)) != 1 {
```
```go
// apps/edge/internal/service/run_dispatch.go:1036
headers := make(map[string]string, len(req.Headers))
for k, v := range req.Headers {
headers[k] = v
}
```
#### 해결 방법
OpenAI server helper를 추가한다. 01의 `openai.provider_auth`가 disabled이면 nil headers를 반환한다. enabled이면 `from_header`에서 raw token을 읽고, `scheme`이 있으면 scheme이 없는 raw token에만 prefix한다. token/header 값은 metadata나 log에 쓰지 않는다.
After:
```go
func (s *Server) providerTunnelAuthHeaders(r *http.Request) (map[string]string, error) {
auth := s.cfg.ProviderAuth
if !auth.Enabled {
return nil, nil
}
raw := strings.TrimSpace(r.Header.Get(auth.FromHeader))
if raw == "" {
if auth.Required {
return nil, errProviderAuthRequired
}
return nil, nil
}
value := raw
if scheme := strings.TrimSpace(auth.Scheme); scheme != "" && !strings.HasPrefix(strings.ToLower(raw), strings.ToLower(scheme)+" ") {
value = scheme + " " + raw
}
return map[string]string{auth.TargetHeader: value}, nil
}
```
Error handling:
- missing required provider auth returns `400 invalid_request_error` before `SubmitProviderTunnel`.
- never echo `raw` token in error body/log.
#### 수정 파일 및 체크리스트
- [ ] `apps/edge/internal/openai/stream.go` 또는 새 small helper file in same package: helper 추가.
- [ ] `apps/edge/internal/openai/server_test.go`: helper behavior는 handler-level tests로 검증.
- [ ] import 추가 시 unused import 없이 정리.
#### 테스트 작성
- `TestProviderTunnelAuthHeadersFormatsRawBearer`: raw `abc` -> `Authorization: Bearer abc`; raw `Bearer abc` -> unchanged.
- helper 단위 테스트 대신 handler test로 충분하면 별도 단위 테스트는 생략 가능하나, missing-required와 successful tunnel forwarding은 반드시 handler test로 둔다.
#### 중간 검증
```bash
go test ./apps/edge/internal/openai -run 'TestProviderTunnelAuthHeaders|TestChatProviderTunnel' -count=1
```
### [SEULGI_TUNNEL-2] Chat Provider Tunnel Injection
#### 문제
`apps/edge/internal/openai/stream.go:356-375``SubmitProviderTunnelRequest` 생성은 `Headers`를 설정하지 않는다. Node adapter는 `apps/node/internal/adapters/openai_compat/openai_compat.go:97-101`에서 tunnel request headers를 provider HTTP request에 적용하므로 Edge에서만 header를 넣으면 된다.
Before:
```go
// apps/edge/internal/openai/stream.go:356
tunnelReq := edgeservice.SubmitProviderTunnelRequest{
NodeRef: dispatch.NodeRef,
ModelGroupKey: strings.TrimSpace(req.Model),
Adapter: dispatch.Adapter,
Target: dispatch.Target,
```
```go
// apps/node/internal/adapters/openai_compat/openai_compat.go:97
a.applyHeaders(httpReq, isJSON)
for k, v := range req.Headers {
httpReq.Header.Set(k, v)
}
```
#### 해결 방법
`submitChatCompletionTunnel` 시작부에서 helper를 호출하고, 생성한 headers를 `SubmitProviderTunnelRequest.Headers`에 넣는다. helper 에러는 tunnel dispatch 전에 caller에게 반환한다.
After:
```go
headers, err := s.providerTunnelAuthHeaders(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid_request_error", "provider auth header is required")
return nil, false
}
tunnelReq := edgeservice.SubmitProviderTunnelRequest{
NodeRef: dispatch.NodeRef,
Headers: headers,
// existing fields...
}
```
#### 수정 파일 및 체크리스트
- [ ] `apps/edge/internal/openai/stream.go`: chat tunnel auth header injection.
- [ ] `apps/edge/internal/openai/server_test.go`: provider auth success/missing tests.
- [ ] 기존 passthrough/sideband tests가 header nil 상태에서 계속 통과하는지 확인.
#### 테스트 작성
- `TestChatProviderTunnelForwardsProviderAuthHeader`: `openai.provider_auth.enabled=true`, caller sends `X-IOP-Provider-Authorization: user-token`, fake service receives `Headers["Authorization"] == "Bearer user-token"`.
- `TestChatProviderTunnelProviderAuthRequired`: header absent, response 400, fake service receives no tunnel request.
- `TestChatProviderTunnelProviderAuthDoesNotReplaceInboundAuth`: `openai.bearer_token`와 provider auth가 동시에 있을 때 inbound `Authorization`은 IOP auth로만 쓰이고 provider header는 `X-IOP-Provider-Authorization`에서 만들어지는지 검증.
#### 중간 검증
```bash
go test ./apps/edge/internal/openai -run 'TestChatProviderTunnelProviderAuth|TestChatProviderTunnelForwardsProviderAuthHeader' -count=1
```
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `apps/edge/internal/openai/stream.go` | SEULGI_TUNNEL-1, SEULGI_TUNNEL-2 |
| `apps/edge/internal/openai/server_test.go` | SEULGI_TUNNEL-1, SEULGI_TUNNEL-2 |
## 최종 검증
```bash
go test ./apps/edge/internal/openai -count=1
```
Expected: OpenAI handler tests pass with fresh execution. Go test cache output is not acceptable for this task; `-count=1` is required.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,143 @@
<!-- task=m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough plan=0 tag=SEULGI_RESPONSES -->
# Code Review Reference - SEULGI_RESPONSES
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## 개요
date=2026-07-09
task=m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough, plan=0, tag=SEULGI_RESPONSES
## Roadmap Targets
- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)
- Task ids:
- `responses-passthrough`: OpenAI-compatible provider route에서 `/v1/responses` raw passthrough가 동작
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. 이 task는 milestone-linked task라 Roadmap Completion을 확인한다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [SEULGI_RESPONSES-1] Route before strict Responses normalization | [ ] |
| [SEULGI_RESPONSES-2] Responses raw tunnel submitter | [ ] |
| [SEULGI_RESPONSES-3] Replace old reject expectations | [ ] |
## 구현 체크리스트
- [ ] [SEULGI_RESPONSES-1] `/v1/responses` handler를 provider route 판단 전 raw body 보존 구조로 바꾼다.
- [ ] [SEULGI_RESPONSES-2] provider route용 Responses raw tunnel submitter/model rewrite를 추가하고 provider auth header를 적용한다.
- [ ] [SEULGI_RESPONSES-3] 기존 reject tests를 passthrough tests로 갱신하고 raw body/stream/auth behavior를 검증한다.
- [ ] `go test ./apps/edge/internal/openai -count=1`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_cloud_G07_N.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_cloud_G07_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-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/``agent-task/archive/YYYY/MM/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-seulgivibe-openai-compatible-provider/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md``CODE_REVIEW-cloud-G07.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가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- Implementation starts only after predecessors `01_config_auth_catalog` and `02+01_provider_tunnel_auth` have `complete.log`.
- Provider `/v1/responses` accepts Codex-like extra fields on passthrough route without weakening normalized non-provider strict parsing.
- Raw body is changed only for model rewrite; output token fields remain Responses-shaped, not Chat-shaped.
- Streaming Responses tunnel relays raw SSE bytes and provider auth header is forwarded.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
### SEULGI_RESPONSES-1 중간 검증
```
$ go test ./apps/edge/internal/openai -run 'TestResponsesProviderTunnelAllowsUnknownFields|TestResponsesProviderPoolDispatch' -count=1
(output)
```
### SEULGI_RESPONSES-2 중간 검증
```
$ go test ./apps/edge/internal/openai -run 'TestResponsesProviderTunnel(SubmitsResponsesPath|RewritesOnlyModel|ForwardsProviderAuthHeader|Streaming)' -count=1
(output)
```
### SEULGI_RESPONSES-3 중간 검증
```
$ go test ./apps/edge/internal/openai -run 'TestResponsesProviderPool|TestResponsesProviderTunnel' -count=1
(output)
```
### 최종 검증
```
$ go test ./apps/edge/internal/openai -count=1
(output)
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

View file

@ -0,0 +1,305 @@
<!-- task=m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough plan=0 tag=SEULGI_RESPONSES -->
# Implementation Plan - SEULGI_RESPONSES
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수 완료 조건이다. 구현 후 검증을 실행하고, 실제 stdout/stderr를 붙이고, active 파일은 그대로 둔 뒤 review ready로 보고한다. selected Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막는 경우에만 review stub의 `사용자 리뷰 요청`에 정확한 근거를 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 외부 secret/service 준비, 검증 증거 공백, 일반 범위 조정은 사용자 리뷰 요청이 아니라 후속 plan 또는 검증 기록으로 처리한다. finalization은 code-review-skill 전용이다.
## 배경
Codex의 Seulgivibe 설정은 `wire_api = "responses"`를 사용할 수 있어야 하지만, 현재 Edge `/v1/responses` handler는 provider-pool/OpenAI-compatible route를 명시적으로 거부한다. Chat Completions에는 raw provider tunnel passthrough가 있으므로 Responses도 같은 raw tunnel path를 제공해야 한다. 이 plan은 01의 config foundation과 02의 provider auth injection을 전제로 `/v1/responses` provider route를 raw passthrough로 여는 작업만 다룬다.
## 사용자 리뷰 요청 흐름
선택된 Milestone lock decision이 실구현을 차단할 때만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 직접 사용자에게 질문하지 않고, code-review가 그 요청을 검증해 `USER_REVIEW.md` 작성 여부를 결정한다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md)
- Task ids:
- `responses-passthrough`: OpenAI-compatible provider route에서 `/v1/responses` raw passthrough가 동작
- Completion mode: check-on-pass
## 분석 결과
### 읽은 파일
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-spec/input/openai-compatible-surface.md`
- `packages/go/config/config.go`
- `apps/edge/internal/openai/routes.go`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/responses_handler.go`
- `apps/edge/internal/openai/stream.go`
- `apps/edge/internal/openai/types.go`
- `apps/edge/internal/openai/server_test.go`
- `apps/edge/internal/service/run_dispatch.go`
- `proto/iop/runtime.proto`
- `apps/node/internal/runtime/types.go`
- `apps/node/internal/adapters/openai_compat/openai_compat.go`
### SDD 기준
- SDD: [SDD.md](agent-roadmap/sdd/routing-policy-model-orchestration/seulgivibe-openai-compatible-provider/SDD.md)
- 상태: `[승인됨]`
- SDD 잠금: 해제
- 대상 Acceptance Scenario:
- S04 -> `responses-passthrough`: Codex-style `/v1/responses` payload를 provider route에서 raw passthrough로 전달하고 model rewrite, streaming, provider auth를 검증한다.
- Evidence Map 반영: 이 plan의 Responses provider tunnel tests가 `responses-passthrough` Roadmap Completion evidence가 된다.
### 테스트 환경 규칙
- `test_env=local`.
- `agent-test/local/rules.md``agent-test/local/edge-smoke.md`를 읽었다.
- 이 subtask는 Edge OpenAI Responses handler/test 변경이다. 적용 명령은 `go test ./apps/edge/internal/openai -count=1`이다.
- live Seulgivibe/Codex CLI smoke는 secret, 사내망, external service에 의존하므로 필수 검증이 아니다. 대신 provider tunnel fake service로 wire path를 검증한다.
### 테스트 커버리지 공백
- 기존 `TestResponsesProviderPoolDispatch`, `TestResponsesProviderPoolAppliesGenerationPolicy`, `TestResponsesProviderPoolThinkingPolicyOverridesStrictOutputDisable`는 provider-pool `/v1/responses`가 reject된다는 과거 behavior를 확인한다.
- provider `/v1/responses` raw body가 model만 rewrite하고 unknown fields/tools/max_output_tokens를 보존하는 테스트 없음.
- streaming Responses passthrough 여부를 검증하는 테스트 없음.
- provider auth header가 Responses tunnel에도 들어가는 테스트 없음.
### 심볼 참조
renamed/removed symbol 없음. 새 helper 후보: `decodeResponsesEnvelope`, `rewriteResponsesModel`, `tunnelResponsesPassthrough`.
### 분할 판단
shared task group은 `m-seulgivibe-openai-compatible-provider`다.
- predecessor `01_config_auth_catalog`: 현재 active predecessor이며 `complete.log` 없음.
- predecessor `02+01_provider_tunnel_auth`: 현재 active predecessor이며 `complete.log` 없음.
- 이 plan은 `03+01,02_responses_passthrough`로 01과 02 모두에 의존한다. 두 predecessor complete 전에는 구현하지 않는다.
### 범위 결정 근거
- Chat Completions passthrough semantics는 필요한 shared helper 호출 외에는 변경하지 않는다.
- normalized non-provider `/v1/responses` path의 기존 strict decode, prompt build, SubmitRun behavior는 유지한다.
- provider tunnel path는 raw provider passthrough로 정의한다: request body는 model rewrite와 provider auth header 외에는 보존한다.
- provider response body model echo rewrite/sideband injection은 하지 않는다. Responses passthrough는 provider-original bytes를 우선한다.
- Node/proto 변경은 하지 않는다.
### 빌드 등급
`cloud-G07`. external CLI/API compatibility와 raw passthrough semantics가 핵심이고, 기존 tests가 반대 behavior를 고정하고 있어 broad context가 필요하다.
## 의존 관계 및 구현 순서
이 plan의 디렉터리명 `03+01,02_responses_passthrough`는 predecessor index `01`, `02`를 요구한다. 구현 시작 전 아래 complete evidence가 필요하다.
- `01`: `agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/complete.log` 또는 matching archive complete.
- `02`: `agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/complete.log` 또는 matching archive complete.
둘 중 하나라도 없으면 구현하지 말고 review stub에 verification blocker로 기록한다.
## 구현 체크리스트
- [ ] [SEULGI_RESPONSES-1] `/v1/responses` handler를 provider route 판단 전 raw body 보존 구조로 바꾼다.
- [ ] [SEULGI_RESPONSES-2] provider route용 Responses raw tunnel submitter/model rewrite를 추가하고 provider auth header를 적용한다.
- [ ] [SEULGI_RESPONSES-3] 기존 reject tests를 passthrough tests로 갱신하고 raw body/stream/auth behavior를 검증한다.
- [ ] `go test ./apps/edge/internal/openai -count=1`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [SEULGI_RESPONSES-1] Route Before Strict Responses Normalization
#### 문제
`apps/edge/internal/openai/responses_handler.go:21-27`은 request body를 바로 strict decoder에 넣고, `29-36`에서 stream/background를 일괄 reject한다. `143-150`의 decoder는 허용된 필드 외 request를 reject한다. provider passthrough에서는 Codex/Responses payload의 unknown fields를 보존해야 하므로, provider route 판단 전에 strict normalized parsing을 강제하면 안 된다.
Before:
```go
// apps/edge/internal/openai/responses_handler.go:21
defer r.Body.Close()
var req responsesRequest
if err := decodeResponsesRequest(json.NewDecoder(r.Body), &req); err != nil {
writeError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
return
}
if req.Stream {
writeError(w, http.StatusBadRequest, "invalid_request_error", "streaming is not supported for /v1/responses")
return
}
```
#### 해결 방법
handler 시작에서 raw body를 읽고, route 판단에 필요한 envelope만 관대하게 decode한다. provider tunnel route면 strict `decodeResponsesRequest`와 prompt normalization을 건너뛴다. non-provider route는 기존 strict decoder와 stream/background reject를 유지한다.
After:
```go
rawBody, err := io.ReadAll(r.Body)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid_request_error", "invalid request body")
return
}
env, err := decodeResponsesEnvelope(rawBody)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
return
}
dispatch, ok := s.resolveRouteDispatch(env.Model)
// validate workspace from env.Metadata
if routeUsesProviderTunnel(dispatch) {
s.tunnelResponsesPassthrough(w, r, env, dispatch, runMeta, rawBody, estimate, contextClass)
return
}
var req responsesRequest
if err := decodeResponsesRequest(json.NewDecoder(bytes.NewReader(rawBody)), &req); err != nil {
// existing normalized behavior
}
```
`decodeResponsesEnvelope``model`, `metadata`, `stream`, `background`만 추출하고 unknown fields를 reject하지 않는다.
#### 수정 파일 및 체크리스트
- [ ] `apps/edge/internal/openai/responses_handler.go`: raw body read, envelope decode, route split.
- [ ] `apps/edge/internal/openai/types.go` 또는 handler-local type: `responsesEnvelope` 추가.
- [ ] existing normalized path behavior가 유지되도록 기존 strict parsing을 non-provider branch에 남긴다.
#### 테스트 작성
- `TestResponsesProviderTunnelAllowsUnknownFields`: provider route에서 `tools`, `parallel_tool_calls`, `store` 같은 unknown field가 있어도 400이 아니고 tunnel body에 보존되는지 검증.
- 기존 normalized route unknown-field rejection test가 있다면 유지한다. 없으면 추가하지 않아도 된다.
#### 중간 검증
```bash
go test ./apps/edge/internal/openai -run 'TestResponsesProviderTunnelAllowsUnknownFields|TestResponsesProviderPoolDispatch' -count=1
```
### [SEULGI_RESPONSES-2] Responses Raw Tunnel Submitter
#### 문제
`apps/edge/internal/openai/responses_handler.go:65-68`은 provider tunnel route를 400으로 막는다. Chat path에는 `apps/edge/internal/openai/stream.go:311-321``342-377`의 tunnel submitter가 있지만 Responses path에는 없다.
Before:
```go
// apps/edge/internal/openai/responses_handler.go:65
if routeUsesProviderTunnel(dispatch) {
writeError(w, http.StatusBadRequest, "invalid_request_error", "/v1/responses is not supported for OpenAI-compatible provider model groups until raw passthrough parity is implemented")
return
}
```
```go
// apps/edge/internal/openai/stream.go:356
tunnelReq := edgeservice.SubmitProviderTunnelRequest{
Path: "/v1/chat/completions",
BuildBody: func(target string) ([]byte, error) {
return rewriteChatCompletionModel(rawBody, target, req)
},
```
#### 해결 방법
Responses 전용 submitter를 추가한다. request path는 `/v1/responses`, body는 `rewriteResponsesModel(rawBody, target)`만 적용한다. 02의 `providerTunnelAuthHeaders`를 호출해 provider auth를 동일하게 적용한다. response writer는 `writeProviderTunnelResponse(w, r, handle, env.Stream, "")`처럼 request model을 비워 model echo rewrite를 끈다.
After:
```go
func (s *Server) tunnelResponsesPassthrough(w http.ResponseWriter, r *http.Request, env responsesEnvelope, dispatch routeDispatch, runMeta map[string]string, rawBody []byte, estimate int, contextClass string) {
headers, err := s.providerTunnelAuthHeaders(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid_request_error", "provider auth header is required")
return
}
tunnelReq := edgeservice.SubmitProviderTunnelRequest{
NodeRef: dispatch.NodeRef,
ModelGroupKey: strings.TrimSpace(env.Model),
Method: http.MethodPost,
Path: "/v1/responses",
Headers: headers,
BuildBody: func(target string) ([]byte, error) {
return rewriteResponsesModel(rawBody, target)
},
Stream: env.Stream,
ProviderPool: dispatch.ProviderPool,
// existing timeout/queue/metadata fields
}
handle, err := s.service.SubmitProviderTunnel(r.Context(), tunnelReq)
// then writeProviderTunnelResponse(..., env.Stream, "")
}
```
`rewriteResponsesModel`은 JSON object의 `model` 필드만 target으로 바꾸고 나머지는 byte-level semantics에 가깝게 보존한다. invalid JSON은 `invalid JSON request`로 reject한다.
#### 수정 파일 및 체크리스트
- [ ] `apps/edge/internal/openai/responses_handler.go`: tunnel branch 추가.
- [ ] `apps/edge/internal/openai/stream.go` 또는 new helper file: `rewriteResponsesModel`/submitter 배치.
- [ ] provider response model echo rewrite를 끄는지 확인.
- [ ] `runMeta`에는 token/header 값을 넣지 않는다.
#### 테스트 작성
- `TestResponsesProviderTunnelSubmitsResponsesPath`: path `/v1/responses`, method POST, no `SubmitRun`.
- `TestResponsesProviderTunnelRewritesOnlyModel`: caller model alias -> provider served model; `max_output_tokens`, `tools`, arbitrary fields 보존.
- `TestResponsesProviderTunnelForwardsProviderAuthHeader`: 02의 auth helper가 Responses tunnel에도 적용됨.
- `TestResponsesProviderTunnelStreaming`: `stream:true`일 때 tunnel request `Stream=true`이고 SSE body가 raw relay됨.
#### 중간 검증
```bash
go test ./apps/edge/internal/openai -run 'TestResponsesProviderTunnel(SubmitsResponsesPath|RewritesOnlyModel|ForwardsProviderAuthHeader|Streaming)' -count=1
```
### [SEULGI_RESPONSES-3] Replace Old Reject Expectations
#### 문제
`apps/edge/internal/openai/server_test.go:5566-5659`의 Responses provider-pool tests는 현재 reject behavior를 전제로 한다. 구현 후 이 테스트를 그대로 두면 새 behavior를 막는다.
#### 해결 방법
기존 test names를 유지하거나 새 names로 교체하되, 기대값을 provider tunnel passthrough로 바꾼다. Provider-pool generation policy는 normalized path의 `providerOptions()`로 변환하지 않는다. Raw Responses passthrough에서는 caller body의 `max_output_tokens`를 보존하고, 누락된 output-token 값에 `max_tokens`를 주입하지 않는다.
#### 수정 파일 및 체크리스트
- [ ] `apps/edge/internal/openai/server_test.go`: old rejection assertions 제거/갱신.
- [ ] `SubmitRun` 미호출, `SubmitProviderTunnel` 호출을 명확히 assert.
- [ ] `staticProviderTunnelFrames` helper 재사용 또는 Responses-specific fixture 추가.
#### 테스트 작성
기존 세 테스트를 새 behavior에 맞게 갱신하고, SEULGI_RESPONSES-2의 새 테스트를 추가한다.
#### 중간 검증
```bash
go test ./apps/edge/internal/openai -run 'TestResponsesProviderPool|TestResponsesProviderTunnel' -count=1
```
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `apps/edge/internal/openai/responses_handler.go` | SEULGI_RESPONSES-1, SEULGI_RESPONSES-2 |
| `apps/edge/internal/openai/stream.go` 또는 new same-package helper | SEULGI_RESPONSES-2 |
| `apps/edge/internal/openai/types.go` | SEULGI_RESPONSES-1, if envelope type is shared |
| `apps/edge/internal/openai/server_test.go` | SEULGI_RESPONSES-1, SEULGI_RESPONSES-2, SEULGI_RESPONSES-3 |
## 최종 검증
```bash
go test ./apps/edge/internal/openai -count=1
```
Expected: OpenAI handler tests pass with fresh execution. Go test cache output is not acceptable for this task; `-count=1` is required.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.