# 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 표준선으로 확정 가능하다. Seulgivibe Claude/OpenAI aliases는 OpenAI-compatible 호출 방식을 지원하므로 model group mixed dispatch에서 passthrough(+sideband) 계열 provider에 포함한다. model group 전역 execution path selector 제거/거부는 Model Group Mixed Provider Dispatch가 소유한다. |

## 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한다.
  - Seulgivibe provider가 model group에 포함될 때 execution path는 provider-derived이며, Seulgivibe aliases는 OpenAI-compatible passthrough(+sideband) 계열 provider로 분류된다.
- 금지:
  - 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`로 변환하지 않는다.
  - Seulgivibe provider가 별도 execution path selector를 정의하지 않는다. model group 전역 selector 제거/거부는 [Model Group Mixed Provider Dispatch](../../../phase/routing-policy-model-orchestration/milestones/model-group-mixed-provider-dispatch.md)가 소유한다.

## 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 | 현재 checkout 검증 기준 | `config-auth-catalog`와 `GOCACHE=/config/workspace/iop/.cache/go-build go test ./packages/go/config -count=1`, `GOCACHE=/config/workspace/iop/.cache/go-build go test ./apps/edge/internal/node -count=1` 결과 |
| S02 | static catalog validation and secret scan | 현재 checkout 검증 기준 | `config-auth-catalog`와 tracked docs/config secret scan 결과 |
| S03 | Edge OpenAI handler auth forwarding/missing tests | 현재 checkout 검증 기준 | `provider-token-tunnel`와 `GOCACHE=/config/workspace/iop/.cache/go-build go test ./apps/edge/internal/openai -count=1` 결과 |
| S04 | Edge OpenAI Responses tunnel tests | 현재 checkout 검증 기준 | `responses-passthrough`와 `GOCACHE=/config/workspace/iop/.cache/go-build go test ./apps/edge/internal/openai -count=1` 결과 |

## Cross-repo Dependencies

- 없음

## Drift Check

- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 현재 checkout 검증 결과로 검증 가능하다. `agent-task` complete 로그는 현재 worktree에 없다.
- [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`를 기본값으로 둔다. Seulgivibe 자체는 별도 execution path selector를 정의하지 않는다. response body model echo rewrite는 하지 않는다.
- 표준선: Seulgivibe Claude/OpenAI aliases는 OpenAI-compatible 호출 방식을 지원하므로 Model Group Mixed Provider Dispatch의 passthrough(+sideband) 계열 provider에 포함한다. mixed provider dispatch 일반화는 별도 Milestone이 소유한다.
- 후속 SDD: 없음
