From 514f9509fc23f01595ea8aa070fa4707acd4b4a2 Mon Sep 17 00:00:00 2001 From: toki Date: Thu, 9 Jul 2026 13:47:20 +0900 Subject: [PATCH] docs: add seulgivibe provider planning --- .../PHASE.md | 4 + .../seulgivibe-openai-compatible-provider.md | 82 +++++ .../SDD.md | 111 ++++++ .../CODE_REVIEW-cloud-G06.md | 152 ++++++++ .../01_config_auth_catalog/PLAN-cloud-G06.md | 335 ++++++++++++++++++ .../CODE_REVIEW-cloud-G07.md | 135 +++++++ .../PLAN-cloud-G07.md | 251 +++++++++++++ .../CODE_REVIEW-cloud-G07.md | 143 ++++++++ .../PLAN-cloud-G07.md | 305 ++++++++++++++++ 9 files changed, 1518 insertions(+) create mode 100644 agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md create mode 100644 agent-roadmap/sdd/routing-policy-model-orchestration/seulgivibe-openai-compatible-provider/SDD.md create mode 100644 agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/CODE_REVIEW-cloud-G06.md create mode 100644 agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/PLAN-cloud-G06.md create mode 100644 agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/CODE_REVIEW-cloud-G07.md create mode 100644 agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/PLAN-cloud-G07.md create mode 100644 agent-task/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/CODE_REVIEW-cloud-G07.md create mode 100644 agent-task/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/PLAN-cloud-G07.md diff --git a/agent-roadmap/phase/routing-policy-model-orchestration/PHASE.md b/agent-roadmap/phase/routing-policy-model-orchestration/PHASE.md index 77300b1..d26430a 100644 --- a/agent-roadmap/phase/routing-policy-model-orchestration/PHASE.md +++ b/agent-roadmap/phase/routing-policy-model-orchestration/PHASE.md @@ -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 방향을 스케치한다. diff --git a/agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md b/agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md new file mode 100644 index 0000000..0872fa4 --- /dev/null +++ b/agent-roadmap/phase/routing-policy-model-orchestration/milestones/seulgivibe-openai-compatible-provider.md @@ -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 정리 +- 확인 필요: 없음 diff --git a/agent-roadmap/sdd/routing-policy-model-orchestration/seulgivibe-openai-compatible-provider/SDD.md b/agent-roadmap/sdd/routing-policy-model-orchestration/seulgivibe-openai-compatible-provider/SDD.md new file mode 100644 index 0000000..760a7c3 --- /dev/null +++ b/agent-roadmap/sdd/routing-policy-model-orchestration/seulgivibe-openai-compatible-provider/SDD.md @@ -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: 없음 diff --git a/agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/CODE_REVIEW-cloud-G06.md b/agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/CODE_REVIEW-cloud-G06.md new file mode 100644 index 0000000..2cf4d96 --- /dev/null +++ b/agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/CODE_REVIEW-cloud-G06.md @@ -0,0 +1,152 @@ + + +# 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-`이면 완료 이벤트 메타데이터를 보고한다. 이 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-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, 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. diff --git a/agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/PLAN-cloud-G06.md b/agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/PLAN-cloud-G06.md new file mode 100644 index 0000000..3739fce --- /dev/null +++ b/agent-task/m-seulgivibe-openai-compatible-provider/01_config_auth_catalog/PLAN-cloud-G06.md @@ -0,0 +1,335 @@ + + +# 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:///anthropic/v1 + models: [claude-sonnet-4-5, claude-opus-4-8, claude-fable-5] + - id: seulgivibe-openai + type: seulgivibe_openai + category: api + endpoint: https:///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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/CODE_REVIEW-cloud-G07.md b/agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..1859b04 --- /dev/null +++ b/agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,135 @@ + + +# 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-`이면 완료 이벤트 메타데이터를 보고한다. 이 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-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, 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. diff --git a/agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/PLAN-cloud-G07.md b/agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/PLAN-cloud-G07.md new file mode 100644 index 0000000..755b293 --- /dev/null +++ b/agent-task/m-seulgivibe-openai-compatible-provider/02+01_provider_tunnel_auth/PLAN-cloud-G07.md @@ -0,0 +1,251 @@ + + +# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/CODE_REVIEW-cloud-G07.md b/agent-task/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..6b41308 --- /dev/null +++ b/agent-task/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,143 @@ + + +# 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-`이면 완료 이벤트 메타데이터를 보고한다. 이 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-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, 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. diff --git a/agent-task/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/PLAN-cloud-G07.md b/agent-task/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/PLAN-cloud-G07.md new file mode 100644 index 0000000..c2d19c0 --- /dev/null +++ b/agent-task/m-seulgivibe-openai-compatible-provider/03+01,02_responses_passthrough/PLAN-cloud-G07.md @@ -0,0 +1,305 @@ + + +# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.